Search code examples
jsonparsinghaskellaeson

Aeson: converting a JSON object to a List of key, value type


I have some JSON fields stored in a database which contain a String -> Double mapping, e.g.:

{
  "some type of thing": 0.45,
  "other type of thing": 0.35,
  "something else": 0.2
}

I want to represent this as a ThingComposition:

data ThingType = ThingTypeSome
               | ThingTypeOther
               | ThingTypeUnknown Text

-- | Create a ThingType from a text representation
txtToThing :: Text -> ThingType
txtToThing "some type of thing"  = ThingTypeSome
txtToThing "other type of thing" = ThingTypeOther
txtToThing s                     = ThingTypeUnknown s

-- Deserialise ThingType from JSON text
instance FromJSON ThingType where
  parseJSON val = withText "ThingType" (return . txtToThing) val

data ThingComposition = ThingComposition [(ThingType, Double)]
                      | InvalidThingComposition

instance FromJSON ThingComposition where
  parseJSON val = withObject "ThingComposition"
                             _
                             val

The _ is what I have no idea how to fill out. I've tried something like the following but I can't get the types to align and I can't work out the best way to do this, given that it's possible that the JSON representation won't match the types, but I don't want to create a list of [(Either String ThingType, Either String Double)]. How can I parse that the object at the top into the ThingComposition type?

_ = (return . ThingComposition) . map (bimap parseJSON parseJSON) . toList

Solution

  • I would make some supporting instances for your ThingType, then reuse the FromJSON (HashMap k v) instance.

    -- added "deriving Eq" to your declaration; otherwise unchanged
    data ThingType = ThingTypeSome
                   | ThingTypeOther
                   | ThingTypeUnknown Text
                   deriving Eq
    
    thingToTxt :: ThingType -> Text
    thingToTxt ThingTypeSome = "some type of thing"
    thingToTxt ThingTypeOther = "other type of thing"
    thingToTxt (ThingTypeUnknown s) = s
    
    instance FromJSONKey ThingType where
        fromJSONKey = FromJSONKeyText txtToThing
    instance Hashable ThingType where
        hashWithSalt n = hashWithSalt n . thingToTxt
    

    With that supporting code, you now have a FromJSON instance for HashMap ThingType Double, which is superior in many ways to a [(ThingType, Double)].