让我们说有一个类似的JSON:
{
"bob_id" : {
"name": "bob",
"age" : 20
},
"jack_id" : {
"name": "jack",
"age" : 25
}
}
是否可以将其解析为[Person]
Person
定义如下?
data Person = Person {
id :: Text
,name :: Text
,age :: Int
}
答案 0 :(得分:7)
您无法从字面上定义[Person]
的实例,因为aeson已经包含[a]
的实例,但您可以创建一个新类型,并为其提供实例。
Aeson还包含实例FromJSON a => FromJSON (Map Text a)
,这意味着如果aeson知道如何解析某些内容,它就知道如何解析该内容的字典。
您可以定义类似于dict中的值的临时数据类型,然后使用Map实例定义FromJSON PersonList
,其中newtype PersonList = PersonList [Person]
:
data PersonInfo = PersonInfo { infoName :: Text, infoAge :: Int }
instance FromJSON PersonInfo where
parseJSON (Object v) = PersonInfo <$> v .: "name" <*> v .: "age"
parseJSON _ = mzero
data Person = Person { id :: Text, name :: Text, age :: Int }
newtype PersonList = PersonList [Person]
instance FromJSON PersonList where
parseJSON v = fmap (PersonList . map (\(id, PersonInfo name age) -> Person id name age) . M.toList) $ parseJSON v
答案 1 :(得分:3)
如果您启用FlexibleInstances
,则可以为[Person]
制作实例。您可以将对象解析为Map Text Value
,然后解析map中的每个元素:
{-# LANGUAGE UnicodeSyntax, OverloadedStrings, FlexibleInstances #-}
module Person (
) where
import Data.Aeson
import Data.Aeson.Types
import Data.Text.Lazy
import Data.Text.Lazy.Encoding
import Data.Map (Map)
import qualified Data.Map as M
data Person = Person {
id ∷ Text,
name ∷ Text,
age ∷ Int }
deriving (Eq, Ord, Read, Show)
instance FromJSON [Person] where
parseJSON v = do
objs ← parseJSON v ∷ Parser (Map Text Value)
sequence [withObject "person"
(\v' → Person i <$> v' .: "name" <*> v' .: "age") obj |
(i, obj) ← M.toList objs]
test ∷ Text
test = "{\"bob_id\":{\"name\":\"bob\",\"age\":20},\"jack_id\":{\"name\":\"jack\",\"age\":25}}"
res ∷ Maybe [Person]
res = decode (encodeUtf8 test)
答案 2 :(得分:2)
mniip's answer将JSON Object
转换为Map
,这会导致按ID排序的结果列表。如果您不需要以这种方式排序结果,那么使用更直接的方法来加快速度可能会更好。特别是,Object
实际上只是HashMap Text Value
,因此我们可以使用HashMap
操作来处理它。
请注意,我将id
字段重命名为ident
,因为大多数Haskell程序员会假设id
引用Prelude
中的标识函数或更一般的标识Control.Category
中的箭头。
module Aes where
import Control.Applicative
import Data.Aeson
import Data.Text (Text)
import qualified Data.HashMap.Strict as HMS
data PersonInfo = PersonInfo { infoName :: Text, infoAge :: Int }
instance FromJSON PersonInfo where
-- Use mniip's definition here
data Person = Person { ident :: Text, name :: Text, age :: Int }
newtype PersonList = PersonList [Person]
instance FromJSON PersonList where
parseJSON (Object v) = PersonList <$> HMS.foldrWithKey go (pure []) v
where
go i x r = (\(PersonInfo nm ag) rest -> Person i nm ag : rest) <$>
parseJSON x <*> r
parseJSON _ = empty
请注意,与Alexander VoidEx Ruchkin's answer一样,这会在PersonInfo
monad中明确地将Person
转换为Parser
。因此,如果Person
未通过某种高级验证,则很容易修改它以产生解析错误。亚历山大的答案也证明了withObject
组合子的实用性,如果我知道它已经存在,我会使用它。