Json parsing in haskell

Я пытаюсь разобрать данные 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

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

Ответ 1

Проблема заключается в том, что Text.JSON не знает, как преобразовать данные JSON в ваш тип данных Person. Для этого вам нужно либо сделать Person, либо экземпляр класса JSON, или вы можете использовать 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. Имена поля в вашем типе записи являются accesor функции.

∀ 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, что приведет к установлению стиля нарушения из-за странных названий полей.

Ответ 2

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

Aeson является стандартом defacto в эти дни, чтобы библиотека использовала все. Пакет 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.