Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

parse json with aeson when first key is variable

Tags:

haskell

My json looks like this

{
    "NW011": {
        "version": 1.0,
        "compatiblehardware": ["N21"],
        "description": "TWIN"
   }
}

The problem is that the key NW011 is variable. I know this key when I start parsing but I have no idea how to declare the data type and instance for aeson.

like image 714
rogergl Avatar asked Jul 31 '26 15:07

rogergl


2 Answers

First, you need to declare a data type that models what your data actually is. I presume that the "NW011" is effectively acting as a kind of field, albeit one that the JSON format in question has promoted to look like a record name. So:

data Thing = Thing {
   thingName :: Text,  -- E.g. "NW011"
   thingVersion :: Text, -- I'm assuming that "Version" could include "1.2.3A" and hence should be a string, not a number.
   thingCompatible :: [Text],
   thingDescription :: Text
}

Now the instance. Normally when you parse JSON you would have an object with fixed field names, so you can use ".:" to extract them. In this case your outermost structure is a one-field object, where the field is called "NW011" and contains a more traditional object.

So you need to write an instance that retrieves this raw object. I'll assume that you could have several such objects with different names like "NW012" etc, so actually your outer structure is a list of these things. If you parse the example you gave then you will get a one-element list.

In Aeson an "Object" is a wrapper around a look-up table (i.e. a HashMap). So all you need to do is iterate through the HashMap extracting the objects by name. So the answer should look something like this (although I haven't tried compiling it):

import qualified Data.HashMap.Strict as H

instance FromJSON [Thing] where
   parseJSON (Object v) = mapM parseItem $ H.toList v
      -- 'v' is a HashMap containing a key "NW011".
   where
      parseItem (k, Object v2) = 
         -- "v2" is a HashMap containing keys for the fixed field names ("version" etc.)
         Thing k <$> 
         v2 .: "version" <*>
         v2 .: "compatiblehardware" <*>
         v2 .: "description"

Note that this just gives the success cases. Your code should also include cases where the arguments to parseJSON and parseItem are not objects.

like image 80
Paul Johnson Avatar answered Aug 02 '26 10:08

Paul Johnson


JSON objects in Aeson are just hash maps. You can extract all the keys if you desire. In this case you can't have a datatype with a field named after your JSON key because that isn't consistent, but you can still parse and use the JSON just fine. Untested code follows.

import Data.Aeson
import Data.Text (Text)
import Data.HashMap.Strict as HMap

parseMyJSON :: Value -> Parser (Text,Value)
parseMyJSON (Object v) =
     case HMap.toList v of
        [(k,v)] -> (k, v)
        _       -> fail "More than one key - who sent this thing?"
parseMyJSON _ = fail "Incorrect JSON - expected an object."
like image 35
Thomas M. DuBuisson Avatar answered Aug 02 '26 08:08

Thomas M. DuBuisson



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!