3

I would like to parse the following JSON using Aeson in Haskell:

{
    "foo": {
        "name": "name 1",
        "location": "location 1"
    },
    "bar": {
        "name": "name 2",
        "location": "location 2"
    }
}

The keys name and location are known, but foo and bar are unknown.

I would like to load the JSON data as a list of the following data type ([Entry]):

data Entry = Entry
        { id :: String       -- "foo" or "bar" etc.
        , name :: String     -- "name 1" or "name 2" etc.
        , location :: String -- "location 1" or "location 2" etc.
        } deriving Show

My first try looks as follows (it does not work, yet):

instance FromJSON Entry where
        parseJSON (Object o) = do
                map (\(id, (name, location)) -> Entry id name location) o
                parseJSON o

How would I parse the JSON correctly?

watain
  • 4,838
  • 4
  • 34
  • 35

1 Answers1

6

This should work:

{-# LANGUAGE FlexibleInstances, OverloadedStrings #-}

import Data.Aeson
import Data.Aeson.Types
import Data.HashMap.Strict

data Entry = Entry
  { id :: String
  , name :: String
  , location :: String
  }
  deriving Show

instance FromJSON [Entry] where
  parseJSON x =
    parseJSON x >>= mapM parseEntry . toList

parseEntry :: (String, Value) -> Parser Entry
parseEntry (i, v) =
  withObject "entry body" (\ o ->
    Entry i <$> o .: "name" <*> o .: "location")
    v

Considering the way you want to parse entries, you cannot really parse single entries, only several at once, because you need the outer object structure.

The JSON parser for [Entry] then proceeds by first parsing the outer object as a hashmap and turning that into a list of pairs using toList, then processing each resulting (String, Value) pair with parseEntry.

kosmikus
  • 19,549
  • 3
  • 51
  • 66
  • 1
    Thanks a lot, it works perfectly. What exactly is the string "entry body" for? – watain Mar 03 '17 at 13:07
  • 3
    @watain It's the argument to pass to `typeMismatch` if you apply the given function is applied to a value of the wrong type. – chepner Mar 03 '17 at 13:18
  • Using this exact example, including the language extensions, with ghc-8.4.3 and aeson-1.3.1.1, I get an "Overlapping instances for FromJSON [Entry]". Has something changed? – rlefevre Sep 04 '18 at 14:34
  • @rlefevre I think GHC 7 used to accept the overlapping instance without requiring an `{-# OVERLAPPING #-}` pragma, whereas GHC 8 is more strict and requires it. – kosmikus Oct 26 '18 at 21:16