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.
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.
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."
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With