使用Aeson解析JSON文档时解析引用

时间:2016-02-27 11:22:22

标签: json haskell aeson

我有一个如下的JSON文档:

{  
   "persons":[  
      {  "id":"343", "name":"John", "age":"45" }
   ],
   "houses":[  
      {  "owner_id":"343" "address":"Charing Cross" }
   ]
}

和Haskell数据类型类似如下:

data City = City { persons :: [Person], houses :: [Houses] }

data Person = Person { personId :: Text, name :: Text }

data House = House { owner :: Person, address :: Text }

在解析Aeson的Value对象时,我希望解析owner_id中的houses引用,并将其转换为完整的Person值。

通常我使用像(.:)这样的好运算符构造Aeson解析器,但是解决引用的需要似乎使这里的事情变得复杂。

有没有办法定义Parser City实现,而不是在JSON对象的基础HashMap中查找键?

1 个答案:

答案 0 :(得分:0)

这项艰巨的任务提供了展示替代"aeson-value-parser" library的强大功能和灵活性的绝佳机会,它提供了基于典型Monadic / Applicative解析器的DSL。

以下输出:

Right (City {cityPersons = [Person {personId = "343", personName = "John"}], cityHouses = [House {houseOwner = Person {personId = "343", personName = "John"}, houseAddress = "Charing Cross"}]})

是以下程序产生的内容:

{-# LANGUAGE NoImplicitPrelude #-}

-- A richer prelude from "rebase"
import Rebase.Prelude
-- The parser API from "aeson-value-parser"
import Aeson.ValueParser
-- A reexport of the original API of "unordered-containers" from "rebase"
import qualified Rebase.Data.HashMap.Strict
-- From "aeson"
import qualified Data.Aeson


main =
  print $
  run city $
  fromJust $
  Data.Aeson.decode $
  "{\"persons\":[{\"id\":\"343\",\"name\":\"John\",\"age\":\"45\"}],\"houses\":[{\"owner_id\":\"343\",\"address\":\"Charing Cross\"}]}"


-- * Model
-------------------------

data City =
  City { cityPersons :: [Person], cityHouses :: [House] }
  deriving (Show)

data Person =
  Person { personId :: Text, personName :: Text }
  deriving (Show)

data House =
  House { houseOwner :: Person, houseAddress :: Text }
  deriving (Show)


-- * Parsers
-------------------------

city :: Value City
city =
  object $ do
    theTable <- field "persons" personsLookupTable
    theHouses <- field "houses" (houses theTable)
    return (City (Rebase.Data.HashMap.Strict.elems theTable) theHouses)

-- |
-- >[  
-- >  { "id":"343", "name":"John", "age":"45" }
-- >]
personsLookupTable :: Value (HashMap Text Person)
personsLookupTable =
  array $
  foldlElements step init personsLookupTableRow
  where
    init =
      Rebase.Data.HashMap.Strict.empty
    step table (key, person) =
      Rebase.Data.HashMap.Strict.insert key person table

-- |
-- >{ "id":"343", "name":"John", "age":"45" }
personsLookupTableRow :: Value (Text, Person)
personsLookupTableRow =
  object $
  (\id name -> (id, Person id name)) <$> id <*> name
  where
    id =
      field "id" string
    name =
      field "name" string

-- |
-- >[  
-- >  { "owner_id":"343" "address":"Charing Cross" }
-- >]
houses :: HashMap Text Person -> Value [House]
houses personsLookupTable =
  array $
  foldrElements (:) [] (house personsLookupTable)

-- |
-- Parses the \"house\" object, using a 'Person' lookup table. 
-- E.g.,
-- >{ "owner_id":"343" "address":"Charing Cross" }
house :: HashMap Text Person -> Value House
house personsLookupTable =
  object $
  House <$> owner <*> address
  where
    owner =
      field "owner_id" (personByID personsLookupTable)
    address =
      field "address" string

-- |
-- Given an ID-lookup table consumes the ID and produces the lookup result.
-- Fails if any of those operations fail.
personByID :: HashMap Text Person -> Value Person
personByID lookupTable =
  string >>= lookup
  where
    lookup key =
      maybe mzero return $
      Rebase.Data.HashMap.Strict.lookup key lookupTable