Aeson: parsing dynamic keys as fields of type

Let's say there is JSON:

{
  "bob_id" : {
    "name": "bob",
    "age" : 20
  },
  "jack_id" : {
    "name": "jack",
    "age" : 25
  }
}

Is it possible to analyze it [Person]with the help Persondefined below?

data Person = Person {
   id   :: Text
  ,name :: Text
  ,age  :: Int
}
+4
source share
3 answers

You cannot define an instance for [Person]literally because aeson already includes an instance for [a], however you can create a new type and provide an instance for this.

Aeson also includes an instance FromJSON a => FromJSON (Map Text a), which means that if aeson knows how to parse something, it knows how to parse a dict of something.

, dict, Map FromJSON PersonList, newtype PersonList = PersonList [Person]:

data PersonInfo = PersonInfo { infoName :: Text, infoAge :: Int }

instance FromJSON PersonInfo where
    parseJSON (Object v) = PersonInfo <$> v .: "name" <*> v .: "age"
    parseJSON _ = mzero

data Person = Person { id :: Text, name :: Text, age :: Int }
newtype PersonList = PersonList [Person]

instance FromJSON PersonList where
    parseJSON v = fmap (PersonList . map (\(id, PersonInfo name age) -> Person id name age) . M.toList) $ parseJSON v
+7

FlexibleInstances, [Person]. Map Text Value, :

{-# LANGUAGE UnicodeSyntax, OverloadedStrings, FlexibleInstances #-}

module Person (
    ) where

import Data.Aeson
import Data.Aeson.Types
import Data.Text.Lazy
import Data.Text.Lazy.Encoding
import Data.Map (Map)
import qualified Data.Map as M

data Person = Person {
    id ∷ Text,
    name ∷ Text,
    age ∷ Int }
        deriving (Eq, Ord, Read, Show)

instance FromJSON [Person] where
    parseJSON v = do
        objs ← parseJSON v ∷ Parser (Map Text Value)
        sequence [withObject "person"
            (\v' → Person i <$> v' .: "name" <*> v' .: "age") obj | 
            (i, obj) ← M.toList objs]

test ∷ Text
test = "{\"bob_id\":{\"name\":\"bob\",\"age\":20},\"jack_id\":{\"name\":\"jack\",\"age\":25}}"

res ∷ Maybe [Person]
res = decode (encodeUtf8 test)
+3

mniip answer JSON Object Map, , . , , , . , Object HashMap Text Value, HashMap .

, id ident, Haskell , id Prelude Control.Category.

module Aes where
import Control.Applicative
import Data.Aeson
import Data.Text (Text)
import qualified Data.HashMap.Strict as HMS

data PersonInfo = PersonInfo { infoName :: Text, infoAge :: Int }

instance FromJSON PersonInfo where
-- Use mniip definition here

data Person = Person { ident :: Text, name :: Text, age :: Int }

newtype PersonList = PersonList [Person]

instance FromJSON PersonList where
  parseJSON (Object v) = PersonList <$> HMS.foldrWithKey go (pure []) v
    where
      go i x r = (\(PersonInfo nm ag) rest -> Person i nm ag : rest) <$>
                     parseJSON x <*> r
  parseJSON _ = empty

Note that, like the Alexander VoidEx Ruchkin answer , this will consistently convert from PersonInfoto Personexplicitly into a monad Parser. Therefore, it would be easy to modify it to create a parsing error if it Persondoes not perform some high-level validation. Alexander's answer also demonstrates the usefulness of the combinator withObject, which I would use if I knew it existed.

+2
source

Source: https://habr.com/ru/post/1606131/


All Articles