15

我正在尝试在 haskell 中解析 JSON 数据。浏览了很多网站,这是我能到达的最远的地方。

data Address = Address { house :: Integer, street :: String, city :: String, state :: String, zip :: Integer } deriving (Show)
data Person = Person { name :: String, age :: Integer, address :: Address } deriving (Show)

getName :: Person -> String
getName (Person n _ _) = n

getAddress :: Person -> Address
getAddress (Person _ _ a) = a

getState :: Address -> String
getState (Address _ _ _ s _) = s

我把它写在一个文件 ex.hs 中并将它加载到 ghci -->

Prelude> import Text.JSON
Prelude Text.JSON> :load ex
Main Text.JSON> let aa = "{\"name\": \"some body\", \"age\" : 23, \"address\" : {\"house\" : 285, \"street\" : \"7th Ave.\", \"city\" : \"New York\", \"state\" : \"New York\", \"zip\" : 10001}}"
...> decode aa :: Result JSValue

它返回

Ok (JSObject (JSONObject {fromJSObject = [("name",JSString (JSONString {fromJSString = "some body"})),("age",JSRational False (23 % 1)),("address",JSObject (JSONObject {fromJSObject = [("house",JSRational False (285 % 1)),("street",JSString (JSONString {fromJSString = "7th Ave."})),("city",JSString (JSONString {fromJSString = "New York"})),("state",JSString (JSONString {fromJSString = "New York"})),("zip",JSRational False (10001 % 1))]}))]}))

不用说,它看起来非常冗长(而且令人恐惧)。我试着做

...> decode aa :: Result Person

它给了我一个错误。如何从此 json 字符串填充 Person 数据结构的实例?例如,我应该怎么做才能获取 JSON 字符串中的人的状态...

4

3 回答 3

27

问题是Text.JSON不知道如何将JSON数据转换为您的Person数据类型。为此,您需要创建类型类Person的实例JSON,或者您可以使用Text.JSON.GenericDeriveDataTypeable扩展来为您完成工作。

泛型

Text.JSON.Generic方法将JSON根据您的数据类型的结构读取结构。

{-# LANGUAGE DeriveDataTypeable #-}
import           Text.JSON.Generic

data Address = Address
    { house  :: Integer
    , street :: String
    , city   :: String
    , state  :: String
    , zip    :: Integer
    } deriving (Show, Data, Typeable)

data Person = Person
    { name    :: String
    , age     :: Integer
    , address :: Address
    } deriving (Show, Data, Typeable)

aa :: String
aa = "{\"name\": \"some body\", \"age\" : 23, \"address\" : {\"house\" : 285, \"street\" : \"7th Ave.\", \"city\" : \"New York\", \"state\" : \"New York\", \"zip\" : 10001}}"

main = print (decodeJSON aa :: Person)

只要您不介意将数据结构中的字段名称与您的JSON格式匹配,此方法就非常有效。

顺便说一句,您不需要编写诸如getNamegetAddress和之类的函数getState。记录类型中的字段名称是存取函数。

∀ x. x ⊦ :t house
house :: Address -> Integer
∀ x. x ⊦ :t address
address :: Person -> Address

JSON 实例

或者,您可以走大路并实现您自己的JSON类实例。

import           Control.Applicative
import           Control.Monad
import           Text.JSON

data Address = Address
    { house  :: Integer
    , street :: String
    , city   :: String
    , state  :: String
    -- Renamed so as not to conflict with zip from Prelude
    , zipC   :: Integer
    } deriving (Show)

data Person = Person
    { name    :: String
    , age     :: Integer
    , address :: Address
    } deriving (Show)

aa :: String
aa = "{\"name\": \"some body\", \"age\" : 23, \"address\" : {\"house\" : 285, \"street\" : \"7th Ave.\", \"city\" : \"New York\", \"state\" : \"New York\", \"zip\" : 10001}}"

-- For convenience
(!) :: (JSON a) => JSObject JSValue -> String -> Result a
(!) = flip valFromObj

instance JSON Address where
    -- Keep the compiler quiet
    showJSON = undefined

    readJSON (JSObject obj) =
        Address        <$>
        obj ! "house"  <*>
        obj ! "street" <*>
        obj ! "city"   <*>
        obj ! "state"  <*>
        obj ! "zip"
    readJSON _ = mzero

instance JSON Person where
    -- Keep the compiler quiet
    showJSON = undefined

    readJSON (JSObject obj) =
        Person       <$>
        obj ! "name" <*>
        obj ! "age"  <*>
        obj ! "address"
    readJSON _ = mzero

main = print (decode aa :: Result Person)

这利用了Result类型是一个事实,Applicative可以轻松地将对JSObject值的查询链接在一起。

这需要做更多的工作,但它可以让您更好地控制结构,JSON如果您必须处理JSON会由于奇怪的字段名称而导致违反样式指南的情况。

于 2013-07-24T20:49:20.447 回答
14

也许游戏有点晚了,但由于这是谷歌返回的第一页,我会试一试。

如今, Aeson是事实上的标准,所以这是每个人都使用的库。Aeson TH包提供了一些很好的功能,可以为您的自定义数据类型自动生成必要的函数。

基本上,您创建与 json 数据相对应的数据类型,然后让 aeson 发挥作用。

{-# LANGUAGE OverloadedStrings,TemplateHaskell #-}
import Data.Aeson
import Data.Aeson.TH
import qualified Data.ByteString.Lazy.Char8 as BL

data Address = Address
    { house  :: Integer
    , street :: String
    , city   :: String
    , state  :: Maybe String
    , zip    :: Integer
    } deriving (Show, Eq)

data Person = Person
    { name    :: String
    , age     :: Integer
    , address :: Address
    } deriving (Show, Eq)

$(deriveJSON defaultOptions ''Address)
$(deriveJSON defaultOptions ''Person)

aa :: BL.ByteString
aa = "{\"name\": \"some body\", \"age\" : 23, \"address\" : {\"house\" : 285, \"street\" : \"7th Ave.\", \"city\" : \"New York\", \"state\" : \"New York\", \"zip\" : 10001}}"

main = print (decode aa :: Maybe Person)

您甚至可以拥有具有Maybe数据类型的可选字段。

于 2015-12-08T19:22:15.723 回答
0

由于答案有点老了,而且 Haskell 生态系统已经有了一些改进,所以让我提一下aesonwithgeneric-aeson似乎是迄今为止最好的选择:

{-# language DeriveGeneric #-}

import GHC.Generics (Generic)
import Generics.Generic.Aeson

data Terms = Terms
  { termsEnd :: Day
  , termsStart :: Day
  , termsType :: Text
  , termsParty :: Text
  , termsState :: Text
  }
  deriving (Generic, Show, Eq)

stripSettings :: Settings
stripSettings = defaultSettings {stripPrefix = Just "terms"}

instance FromJSON Terms where parseJSON = gparseJsonWithSettings stripSettings

我开始使用Chris Penner 的 JSON to Haskell 工具(它本身就很棒),然后添加了泛型实例以使其无需样板即可工作。

我认为这是迄今为止最好的解决方案,因为它允许您解码 JSON 字段,即使它们使用Haskell 关键字(例如type,在我的示例中)通过gparseJsonWithSettings自动解析日期Data.Time.Calendar.Day无缝工作!

于 2021-12-17T11:44:37.073 回答