Search code examples
jsonparsinghaskellaeson

Parsing JSON to Map String String with Aeson


I am trying to parse a JSON from API with a data structure like

{
  "en": {
    "translation": {
      "name": "Name",
      "description": ["I am a", "en person"]
    }
  },
  "jp": {
    "translation": {
      "name": "JP Name",
      "description": ["I am a", "jp person"]
    }
  }
}

So I will just want to parse them to something like newtype Translations = Map String String. locale will be key name and the value will be just the string of JSON translation like {"name": "Name", "description": ["I am a", en person"]} because the value could be arbitrarily complex and I dun care/need to convert it to other Haskell data structure.

I tried so many way to write a proper parseJSON for Translation but still can't make it.

Any help would be appreciated!


Solution

  • We can decode the ByteString with:

    Prelude Data.Aeson Bs Hm Mp Tx> decode text :: Maybe Object
    Just (fromList [("jp",Object (fromList [("translation",Object (fromList [("name",String "JP Name"),("description",Array [String "I am a",String "jp person"])]))])),("en",Object (fromList [("translation",Object (fromList [("name",String "Name"),("description",Array [String "I am a",String "en person"])]))]))])
    

    So then we only need to perform a mapping on the HashMap Text Object wrapped in the `Object constructor:

    import Data.Aeson
    import qualified Data.ByteString.Lazy.Char8 as Bs
    import qualified Data.HashMap.Lazy as Hm
    import qualified Data.Map as Mp
    import qualified Data.Text as Tx
    
    process :: Value -> Maybe (Mp.Map String String)
    process (Object m) = Just ((Mp.fromList . map f) (Hm.toList m))
        where f (x, y) = (Tx.unpack x, Bs.unpack (encode y))
    process _ = Nothing
    

    We then obtain a Map String String wrapped in a Maybe (since both decoding, and the processing can go wrong, it is probably better to use a Maybe), that maps Strings on Strings:

    Prelude Data.Aeson Bs Hm Mp Tx> decode text >>= process
    Just (fromList [("en","{\"translation\":{\"name\":\"Name\",\"description\":[\"I am a\",\"en person\"]}}"),("jp","{\"translation\":{\"name\":\"JP Name\",\"description\":[\"I am a\",\"jp person\"]}}")])
    

    That being said, I'm not sure that a JSON blob as value is here what you want, since now one can not "look into" the value and inspect what is inside that element. Furthermore if you want to do a lot of lookups, Text is typically an order of magnitude faster when you want to check if two Texts are the same.