2013-07-24 4 views
10

Я пытаюсь разобрать данные JSON в haskell. Пройдя через множество веб-сайтов, это самое дальнее, с чем я смог добраться.json parsing in 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 

, и это дало мне ошибку. Как я могу заполнить экземпляр структуры данных Person из этой строки json? Например, что я должен сделать, чтобы получить состояние человека в строку JSON ...

ответ

22

Проблема заключается в том, что Text.JSON не знает, как конвертировать JSON данные в вашего типа Person данных. Для этого вам нужно либо сделать Person, либо экземпляром JSON typeclass, либо можете использовать Text.JSON.Generic и расширение DeriveDataTypeable, чтобы выполнить работу за вас.

Дженерики

Метод 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.

Как и в сторону, вам не нужно писать такие функции, как getName, getAddress, и getState. Имена поля в вашем типе записи - это функции .

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

JSON Instance

В качестве альтернативы, вы можете взять на себя большую дорогу и реализовать свой собственный экземпляр 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, что приведет к STYLE основного положению нарушений из-за странные имена полей.

+0

Возможно, вам также следует привести пример создания экземпляра JSON, поскольку вы упомянули его в качестве альтернативы. – Wes

+0

@Wes, Там вы идете. – sabauma

+0

Очень полезная информация. У меня есть вопрос. Помимо 'Text.JSON.Generic' (какой пакет он исходит?), Я также нашел https://hackage.haskell.org/package/generic-aeson, аналогично используя механизм Generics, чтобы сделать экземпляры JSON Haskell данные. Каковы различия между этими двумя пакетами? –

5

Возможно, немного поздно в игре, но так как это первая страница google, я верну ее.

Aeson является стандартом defacto в эти дни, так что это библиотека, которую все используют. Пакет Aeson TH предлагает отличную функциональность для автоматического создания необходимых функций для ваших пользовательских типов данных.

В основном вы создаете свои типы данных, соответствующие данным json, а затем позволяете аэсону делать магию.

{-# 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