bruteforcecat
bruteforcecat

Reputation: 100

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!

Upvotes: 1

Views: 676

Answers (2)

max taldykin
max taldykin

Reputation: 12908

There are instances of FromJSON for Map and HashMap. So there is no need to match on Object constructor, you can just specify required type and Aeson.decode:

> import qualified Data.Aeson as Aeson
> import Data.Map as Map
> let Just obj = Aeson.decode str :: Maybe (Map String Aeson.Value)

> obj
fromList [("jp",Object (fromList [("translation",Object (fromList [("description",Array [String "I am a",String "jp person"]),("name",String "JP Name")]))])),("en",Object (fromList [("translation",Object (fromList [("description",Array [String "I am a",String "en person"]),("name",String "Name")]))]))]

> mapM_ print $ Map.toList $ Map.map Aeson.encode obj
("jp","{\"translation\":{\"description\":[\"I am a\",\"jp person\"],\"name\":\"JP Name\"}}")
("en","{\"translation\":{\"description\":[\"I am a\",\"en person\"],\"name\":\"Name\"}}")

Upvotes: 1

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 477533

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.

Upvotes: 1

Related Questions