2013-07-24 20 views
10

私はhaskellでJSONデータを解析しようとしています。たくさんのウェブサイトを見てきて、これは私が得ることができる最も遠いものです。jsonのhaskellでの解析

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文字列に人の状態を取得するために何をすべき...

答えて

22

問題はText.JSONがあなたのPersonデータ型を するJSONデータを変換する方法を知らないということです。これを行うには、タイプのPersonと のインスタンスを作成するか、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タイプが一緒JSObject値にクエリApplicative容易に 鎖であるという事実を利用します。

これはもう少し作業ですが、奇妙なフィールド名によるスタイルガイドライン 違反の原因となることJSONに対処する必要があれば、それはあなたの JSONの構造をより詳細に制御できます。

+0

JSONのインスタンスを作成する方法の例として、代わりに挙げたことがあります。 – Wes

+0

@Wes、そこに行きます。 – sabauma

+0

非常に有用な情報です。質問があります。 'Text.JSON.Generic'(このパッケージの由来は?)とは別に、https://hackage.haskell.org/package/generic-aesonも同様にジェネリックの機械を使ってHaskellのJSONインスタンスを作成していますデータ。これらの2つのパッケージの違いは何ですか? –

5

ゲームで少し遅いかもしれませんが、これが最初のページであるため、Googleが返すものです。

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データ型のオプションフィールドを使用することもできます。

+0

Aesonは 'json'パッケージよりも多くのライブラリをリンクしなければなりません – dani24