【问题标题】:Resolving references while parsing a JSON document with Aeson使用 Aeson 解析 JSON 文档时解析引用
【发布时间】:2016-02-27 11:22:22
【问题描述】:

我有一个如下 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 对象时,我想解析 houses 中的 owner_id 引用并将它们转换为成熟的 Person 值。

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

有没有一种方法可以定义一个Parser City 实现而不求助于在 JSON 对象的底层 HashMap 中查找键?

【问题讨论】:

  • 我建议您解析文字 json 对象、引用和所有内容,然后将其转换为您喜欢的任何形式。您可以通过将House 参数化:data House a = House { a :: person, ..} 轻松做到这一点。然后为City PersonId 编写一个解析器并拥有一个函数City PersonId -> City Person。编写同时尝试做这两件事的代码,你将获得什么好处?我什么都看不到。至少有两个缺点:代码复杂性大大增加,可维护性降低。
  • 为什么不按ID将所有人放在一个imap中,你可以直接查找。

标签: json haskell aeson


【解决方案1】:

这项不平凡的任务提供了一个完美的机会来展示替代 "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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-10
    相关资源
    最近更新 更多