I'm trying to parse JSON data in haskell. Having gone through a slew of websites, this is the furthest I have been able to get to.
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
I write that in a file ex.hs and load it 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
It returns
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))]}))]}))
Needless to say, it seems pretty verbose (and frightening). I tried doing
...> decode aa :: Result Person
and it gave me an error. How do I go about populating an instance of the Person datastructure from this json string? For example, what should I do to get the state of the person in the JSON string...
The problem is that Text.JSON
does not know how to convert JSON
data to
your Person
data type. To do this, you need to either make Person
and
instance of the JSON
typeclass, or your can use Text.JSON.Generic
and the
DeriveDataTypeable
extension to do the work for you.
The Text.JSON.Generic
method will read the JSON
structure based on the
structure of your data type.
{-# 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)
This method works really well as long as you don't mind matching the names of the fields
in your data structure to your JSON
format.
As an aside, you don't need to write functions like getName
, getAddress
,
and getState
. The names of the field in your record type are accesor
functions.
∀ x. x ⊦ :t house
house :: Address -> Integer
∀ x. x ⊦ :t address
address :: Person -> Address
Alternatively, you could take the high road and implement your own instance of
the JSON
class.
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)
This takes advantage of the fact that the Result
type is an Applicative
to easily
chain together queries on the JSObject
value.
This is a little more work, but it gives you more control of the structure of
the JSON
if you have to deal with JSON
that will cause style guideline
violations due to weird field names.