0

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

matt
  • 1,817
  • 14
  • 35

1 Answers1

2

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.

bheklilr
  • 53,530
  • 6
  • 107
  • 163
  • thank you for your response. I tried your code with the amended JSON: `decode json :: Maybe Body` = **Nothing**. What am I doing wrong? – matt Dec 03 '15 at 20:38
  • @matthias Try `decode json :: Maybe Response`, because your JSON document contains a `Response` which has multiple `Body`s in it. – bheklilr Dec 03 '15 at 20:57
  • yes that works. Am I not able to access the other objects directly? – matt Dec 03 '15 at 21:12
  • @matthias I'm not sure what you're wanting to do, in order to get at values in your JSON file (which is just a `ByteString` before parsing, there are no "objects" in it) you have to parse the entire thing. In order to access the other objects you'll have to drill down into the structure after it's parsed. You could parse it into aeson's generic `Value` type, then extract out just the nodes you want to turn into your custom data type, but you'd be better off with what's above and then using the record field accessors as normal (or using something like `lens`). – bheklilr Dec 03 '15 at 21:16
  • understood. Thanks a lot for your help. – matt Dec 03 '15 at 21:31
  • sorry, I am back. What would be the most efficient way to create a list of all the values from the decoded JSON ie `[[ 5555, 10, 1, ["ABCDEF", "1111", "2222"], 17, true, 0, 0, 0, 0, 0, false, 7, "T1", [[1, "option1", 5, "5"],[2, "option2", 14, "OS"]]], [ ... second list], ...]`. if it is too involved could you just point me in the right direction to learn. Thanks again! – matt Dec 04 '15 at 14:36
  • @matthias First of all you can't have heterogenous lists in Haskell (at least not without a fair bit of extra work that would be unnecessary here). In this case I would recommend parsing everything as the aeson `Value` type then doing the conversion to an `Array` (one of the constructors of `Value`) for every sub-node. – bheklilr Dec 04 '15 at 14:40
  • sorry, you seem to be one of the people answering all the haskell threads, so I guess I'll ask you again if you don't mind. What is the best way to access an individual field in the decoded json. Say, I want to access the 2 `"sku"`s or even the `identity` types as a whole. This is my last hurdle to getting what I want . I appreciate it! – matt Dec 04 '15 at 19:23
  • @matthias Stackoverflow policy is that if you have a followup question it's usually best to ask it as a full post rather than in the comments (this keeps things more discoverable by others in the future, and allows for more room to answer questions). This question has a more wordy answer than what I could fit in ~500 characters, and would benefit from full formatting. Please feel free to submit a new question detailing what you want to do and what you've tried. Remember to keep the new question self-contained so that others don't have to refer back to this one to understand the context. – bheklilr Dec 04 '15 at 19:30
  • done! Are there any amendments I should make to this post? – matt Dec 04 '15 at 19:55