matt
matt

Reputation: 2069

How to parses nested JSON, which also contains lists,in Haskell?

I am trying to parse the fallowing JSON with aseon:

JSON

{
  "response": [
    {
      "id": 5555,
      "brandId": 10,
      "productTypeId": 1,
      "identity": {
        "sku": "ABCDEF",
        "ean": "1111",
        "barcode": "2222"
      },
      "productGroupId": 17,
      "stock": {
        "stockTracked": true,
        "weight": {
          "magnitude": 0
        },
        "dimensions": {
          "length": 0,
          "height": 0,
          "width": 0,
          "volume": 0
        }
      },
      "financialDetails": {
        "taxable": false,
        "taxCode": {
          "id": 7,
          "code": "T1"
        }
      },
      "variations": [
        {
          "optionId": 1,
          "optionName": "option1",
          "optionValueId": 5,
          "optionValue": "5"
        },
        {
          "optionId": 2,
          "optionName": "option2",
          "optionValueId": 14,
          "optionValue": "OS"
        }
      ]
    },
    {
      "id": 9999,
      "brandId": 10,
      "productTypeId": 1,
      "identity": {
        "sku": "CDEFG",
        "ean": "111221",
        "barcode": "2443222"
      },
      "productGroupId": 17,
      "stock": {
        "stockTracked": true,
        "weight": {
          "magnitude": 0
        },
        "dimensions": {
          "length": 0,
          "height": 0,
          "width": 0,
          "volume": 0
        }
      },
      "financialDetails": {
        "taxable": false,
        "taxCode": {
          "id": 7,
          "code": "T1"
        }
      },
      "variations": [
        {
          "optionId": 1,
          "optionName": "option1",
          "optionValueId": 5,
          "optionValue": "5"
        },
        {
          "optionId": 2,
          "optionName": "option2",
          "optionValueId": 14,
          "optionValue": "14"
        }
      ]
    } 
  ]
}

I have tried to set up the data structures and instances: Here is what I have so far:

CODE

{-# LANGUAGE OverloadedStrings #-}

import Data.Aeson 
import Control.Applicative 
import qualified Data.ByteString.Lazy.Char8 as BS



data Response = Response                    { response              :: [Body]
                                            } deriving (Show)

instance FromJSON Response where
    parseJSON (Object v) = Response <$>
                           v .: "response"                                             

data Body = Body                            { idd                   :: Int
                                            , brandId               :: Int
                                            , productTypeId         :: Int
                                            , identity              :: Identity
                                            , productGroupId        :: Int
                                            , stock                 :: Stock
                                            , financialDetails      :: FinancialDetails
                                            , variations            :: [Variation]
                                            } deriving (Show)

instance FromJSON Body where
    parseJSON (Object v) = Body <$>
                           (e >>= (.: "id")) <*>
                           (e >>= (.: "brandId")) <*>
                           (e >>= (.: "productTypeId")) <*>
                         -- DON'T KNOW HOW TO SET UP  IDENTITY  
                           (e >>= (.: "productGroupId"))
                         -- DON'T KNOW HOW TO SET UP STOCK  
                         -- DON'T KNOW HOW TO SET UP FINANCIAL DETAILS                          
                         -- DON'T KNOW HOW TO SET UP VARIATIONS  
                           where e = (v .: "response")                                              


data Identity = Identity                    { sku                   :: String
                                            , ean                   :: String
                                            , barcode               :: String 
                                            } deriving (Show)

data Stock = Stock                          { stockTracked          :: Bool
                                            , weight                :: Weight
                                            , dimensions            :: Dimensions
                                            } deriving (Show)

data Weight = Weight                        { magnitude             :: Double
                                            } deriving (Show)

data Dimensions = Dimensions                { length                :: Double
                                            , height                :: Double
                                            , width                 :: Double
                                            , volume                :: Double  
                                            } deriving (Show)       

data FinancialDetails = FinancialDetails    { taxable               :: Bool
                                            , taxCode               :: TaxCode
                                            } deriving (Show)

data TaxCode = TaxCode                      { id                    :: Int
                                            , code                  :: String
                                            } deriving (Show)    


data Variation = Variation                  { optionId              :: Int
                                            , optionName            :: String
                                            , optionValueId         :: Int
                                            , optionValue           :: String
                                            }  deriving (Show)  

My problems are that I do not know how to bind the nested values nor can I figure out how to handle the list portions of the JSON. I have tried to go through the documentation and other stackoverflow questions; however I cannot find anything to help with this level of complexity.

How would I go about getting objects for the nested values and for the listed values.

Thanks

Upvotes: 0

Views: 187

Answers (1)

bheklilr
bheklilr

Reputation: 54078

You just need to finish writing all the FromJSON instances for each type, although you need to fix up your instance for Body and each definition of parseJSON should be total functions, so include a case for when you are given something other than an Object:

data Response = Response
    { response :: [Body]
    } deriving (Show)

instance FromJSON Response where
    parseJSON (Object v) = Response <$> v .: "response"
    parseJSON _ = mzero

data Body = Body
    { idd               :: Int
    , brandId           :: Int
    , productTypeId     :: Int
    , identity          :: Identity
    , productGroupId    :: Int
    , stock             :: Stock
    , financialDetails  :: FinancialDetails
    , variations        :: [Variation]
    } deriving (Show)

instance FromJSON Body where
    parseJSON (Object v) = Body
        <$> v .: "id"
        <*> v .: "brandId"
        <*> v .: "productTypeId"
        <*> v .: "identity"
        <*> v .: "productGroupId"
        <*> v .: "stock"
        <*> v .: "financialDetails"
        <*> v .: "variations"
    parseJSON _ = mzero

Then you just need to write each of the parsers for your other types:

data Identity = Identity
    { sku       :: String
    , ean       :: String
    , barcode   :: String
    } deriving (Show)

instance FromJSON Identity where
    parseJSON (Object v) = Identity
        <$> v .: "sku"
        <*> v .: "ean"
        <*> v .: "barcode"
    parseJSON _ = mzero

data Stock = Stock
    { stockTracked  :: Bool
    , weight        :: Weight
    , dimensions    :: Dimensions
    } deriving (Show)

instance FromJSON Stock where
    parseJSON (Object v) = Stock
        <$> v .: "stockTracked"
        <*> v .: "weight"
        <*> v .: "dimensions"
    parseJSON _ = mzero

And so on. I'll let you finish out all the different types you have here.

One last little note, it appears as though you had a copy/paste error with your sample JSON file, lines 4 and 48 need commas at the end to make it a valid JSON document.

Alternatively, you could include the DeriveGeneric language extension, then with an import of GHC.Generics you can deriving (Show, Generic) for each of your types. Then all you need to write is

instance FromJSON Response where
instance FromJSON Body where
instance FromJSON Identity where
instance FromJSON Stock where
instance FromJSON Weight where
instance FromJSON Dimensions where
instance FromJSON FinancialDetails where
instance FromJSON TaxCode where
instance FromJSON Variation where

without needing to specify the actual details of how to convert from JSON. The only problem now is that you need the JSON to use the keys "idd" instead of "id" for the Body structures, although I would recommend renaming it to be "bodyId" anyway since id is a built-in function in Haskell (and similarly with the other types that have an id field). You can then also have instance ToJSON <type> where for each of your types in order to automatically get serialization of your types.

Upvotes: 2

Related Questions