2015-09-06 42 views
4

比方說有像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

爲什麼不可能?只是將結果解析爲「摺疊」以將其轉換爲「[Person]」?但我從未使用過'aeson'。也許說明問題更清楚。 – jakubdaniel

回答

7

不能爲[Person]字面定義一個實例,因爲埃宋已經包含了[a]一個實例,但是你可以創建一個NEWTYPE,並提供一個實例。

埃宋還包括實例FromJSON a => FromJSON (Map Text a),如果埃宋知道如何分析的東西,這意味着,它知道如何解析的的東西的字典。

您可以定義一個臨時的數據類型相似的字典的值,然後使用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 
3

如果啓用FlexibleInstances,可以使實例[Person]。你可以分析你的對象Map Text Value,然後分析每個元素在地圖:

{-# 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) 
+1

如果我們要實例化'FromJSON Person',那麼不會產生不一致的感染 – mniip

2

mniip's answer的JSON Object轉換爲Map,從而導致由ID排序的結果列表。如果您不需要按照這種方式排序的結果,那麼最好使用更直接的方法來加快速度。特別是,Object實際上只是一個HashMap Text Value,所以我們可以使用HashMap操作來處理它。

注意,我改名idident,因爲大多數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,此序列明確地內Parser單子從PersonInfoPerson轉換。因此,如果Person未通過某種高級別驗證,那麼修改它可能會產生解析錯誤。亞歷山大的回答也證明了withObject combinator的實用性,如果我知道它的存在,我會使用它。