Aeson:FromJSON与嵌套编码的json

时间:2016-12-18 11:15:19

标签: haskell aeson

我的JSON在其中一个属性中包含一个编码的JSON字符串:

{ 
  "firstName": "Frederick",
  "lastName": "Krueger",
  "address": "{\"street\": \"Elm Street, 13\", \"city\": \"Springwood\", \"state\": \"OH\"}"
}

鉴于我有一个数据类型:

data Address = Address { street :: String, city :: String, state :: String }
               deriving (Generic, Show)

data Person = Person { firstName :: String, lastName :: String, address :: Address }
              deriving (Generic, Show)

如何为FromJSON实施Person

2 个答案:

答案 0 :(得分:1)

这是我尝试使用小帮手解析器:

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}

module MyModule where

import           Control.Monad
import           Data.Aeson
import           Data.Aeson.Types
import           Data.ByteString (ByteString)
import qualified Data.Text.Encoding as Text
import           GHC.Generics

data Address = Address
  { street :: String
  , city :: String
  , state :: String
  } deriving (Generic, Show)

instance FromJSON Address

data Person = Person
  { firstName :: String
  , lastName :: String
  , address :: Address
  } deriving (Generic, Show)

instance FromJSON Person where
  parseJSON (Object o) =
    Person <$> o .: "firstName" <*> o .: "lastName" <*>
    (parseAddress =<< o .: "address")
  parseJSON _ = mzero

parseAddress :: Value -> Parser Address
parseAddress (String s) = do
  let maybeObject = decodeStrict (Text.encodeUtf8 s)
  case maybeObject of
    Nothing -> mzero
    Just o -> parseJSON (Object o)
parseAddress _ = mzero

testString :: ByteString
testString =
  "{\n  \"firstName\": \"Frederick\",\n  \"lastName\": \"Krueger\",\n\"address\": \"{\\\"street\\\": \\\"Elm Street, 13\\\", \\\"city\\\": \\\"Springwood\\\", \\\"state\\\": \\\"OH\\\"}\"\n}\n"

并在repl中运行它:

λ> eitherDecodeStrict testString :: Either String Person
Right (Person {firstName = "Frederick", lastName = "Krueger", address = Address {street = "Elm Street, 13", city = "Springwood", state = "OH"}})

简而言之,Person实例使用=<<运算符将两个解析器链接在一起 - 由o .: "address"parseAddress生成的解析器。在parseAddress内,如果我们看到String,我们就可以检查该值并进行进一步处理。 decodeStrict用于尝试将字符串解码为对象,parseAddress parseJSON可以使用Address将该对象解析为parseAddress :: Value -> Parser Address parseAddress (String s) = either (const mzero) parseJSON (eitherDecodeStrict (Text.encodeUtf8 s)) parseAddress _ = mzero

或者作为没有中间值的单线条:

vertical-align

答案 1 :(得分:0)

这样做的一种方法是使用函数withObject编写自己的解析器。在该功能中,您明确处理您的地址案例。例如:

parsePerson :: Value -> Parser Person
parsePerson = withObject "expected person"
              (\obj -> do
                 fname <- obj .: "firstName"
                 lname <- obj .: "lastName"
                 (addr :: String) <- obj .: "address"
                 let addr' = decode (BS.fromStrict $ pack addr)
                 return $ Person { firstName = fname, lastName = lname, address = fromJust addr' })

instance FromJSON Address where
    parseJSON (Object v) = Address <$>
                           v .: "street" <*>
                           v .: "city" <*>
                           v .: "state"
    parseJSON _ = empty

这些是我必须做的必要的导入:

import Data.Aeson
import Data.Aeson.Types (Parser(..), parseEither)
import Data.Maybe (fromJust)
import Data.Text (Text)
import Data.ByteString.Char8 (pack)
import qualified Data.ByteString.Lazy as BS

请注意,在您的代码中,您可能希望避免使用部分函数fromJust。将JSON字符串存储在名为add.json

的文件中时解码JSON字符串的示例方法
main :: IO ()
main = do
  dat <- BS.readFile "./add.json"
  let val = decode dat :: Maybe Value
  print $ (parseEither parsePerson (fromJust val))

执行时输出:

sibi::jane { ~/scripts }-> ./aeson.hs
Right (Person {firstName = "Frederick", lastName = "Krueger", address = Address {street = "Elm Street, 13", city = "Springwood", state = "OH"}})