2013-07-24 34 views
10

Ich versuche, JSON-Daten in Haskell zu analysieren. Nachdem ich eine ganze Reihe von Websites durchsucht habe, ist dies das weiteste, was ich erreichen konnte.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 

ich schreiben, dass in einer Datei ex.hs und laden Sie es in 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 

Es gibt

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))]}))]})) 

Unnötig zu sagen, es ist ziemlich ausführliche scheint (und erschreckend). Ich versuchte es

...> decode aa :: Result Person 

und es gab mir einen Fehler. Wie gehe ich vor, um eine Instanz der Person-Datenstruktur aus dieser json-Zeichenfolge zu füllen? Zum Beispiel, was soll ich tun, um den Zustand der Person in dem JSON-String zu erhalten ...

Antwort

22

Das Problem ist, dass Text.JSON nicht weiß, wie JSON Daten Ihren Person Datentyp zu konvertieren. Um dies zu tun, müssen Sie entweder Person und Instanz der JSON Typklasse machen, oder Sie können Text.JSON.Generic und die DeriveDataTypeable Erweiterung verwenden, um die Arbeit für Sie zu erledigen.

Generics

Die Text.JSON.Generic Methode wird die JSON Struktur basierend auf der Struktur Ihres Datentypen lesen.

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

Diese Methode funktioniert sehr gut, solange Sie nicht die Namen der Felder in Ihrer Datenstruktur zu Ihrem JSON Format passend nichts ausmachen.

Als Nebenwirkung, Sie brauchen keine Funktionen zu schreiben wie getName, getAddress, und getState. Die Namen des Feldes in Ihrem Datensatztyp sind Funktionen.

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

JSON Instanz

Alternativ können Sie die hohe Straße nehmen und Ihre eigene Instanz von die JSON Klasse implementieren.

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) 

Dies nutzt die Tatsache, dass der Result Typ ein Applicative zu leicht Kette zusammen Abfragen auf dem JSObject Wert.

Dies ist ein wenig mehr Arbeit, aber es gibt Ihnen mehr Kontrolle über die Struktur der die JSON, wenn Sie mit JSON zu tun haben, die Verletzungen Stil Richtlinie aufgrund seltsam Feldnamen verursacht.

+0

Vielleicht sollten Sie auch ein Beispiel zum Erstellen einer Instanz von JSON geben, da Sie es als eine Alternative erwähnt haben. – Wes

+0

@Wes, los gehts. – sabauma

+0

Sehr nützliche Informationen. Ich habe eine Frage. Abgesehen von 'Text.JSON.Generic' (welches Paket kommt es her?), Habe ich auch https://hackage.haskell.org/package/generic-aeson gefunden, das ähnlich die Generics-Maschinerie verwendet, um JSON-Instanzen von Haskell zu machen Daten. Was sind die Unterschiede zwischen diesen beiden Paketen? –

5

Vielleicht ein bisschen spät im Spiel, aber da dies die erste Seite ist, die Google zurückgibt, werde ich es versuchen.

Aeson ist heutzutage der Defacto-Standard, das ist die Bibliothek, die jeder benutzt. Das Paket bietet einige nützliche Funktionen zum automatischen Generieren der erforderlichen Funktionen für Ihre benutzerdefinierten Datentypen.

Im Grunde erstellen Sie Ihre Datentypen, die den JSON-Daten entsprechen und dann lassen Sie AESON die Magie tun.

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

Sie können auch optionale Felder mit dem Maybe Datentyp haben.

+0

Aeson muss viel mehr Bibliotheken als das 'json' Paket verbinden – dani24