2016-02-27 70 views
1

我有类似下面的JSON文件:解析引用在解析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 } 

在解析埃宋的Value对象,我想以解决owner_id参考文献houses并将其转化为完整的Person值。

通常我会用像(.:)这些漂亮的运算符构造Aeson解析器,但解析引用的需要似乎会使事情变得复杂。

有没有办法定义一个Parser City实现,它不会在JSON对象的底层HashMap中查找关键字?

+6

我建议你解析文字JSON对象,参考文献和所有的,然后将其转换为两者形成你喜欢。您可以通过参数化'House'来轻松完成此操作:'数据House a = House {a :: person,..}'。然后你为'City PersonId'编写一个解析器,并且有一个函数'City PersonId - > City Person'。通过编写同时试图完成这两件事的代码,您将获得什么优势?我看不到任何东西。至少有两个缺点:代码复杂性大大增加,可维护性降低。 – user2407038

+0

为什么不把所有的人都放在一个imap上,你可以直接查看。 –

回答

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