Pertanyaan Aeson: menguraikan kunci dinamis sebagai bidang jenis


Katakanlah ada JSON seperti:

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

Apakah mungkin untuk menguraikannya [Person] dengan Person didefinisikan seperti di bawah ini?

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

4
2017-09-06 09:07


asal


Jawaban:


Anda tidak dapat menentukan instance untuk [Person] secara harfiah, karena aeson sudah termasuk contoh untuk [a], namun Anda dapat membuat jenis baru, dan memberikan contoh untuk itu.

Aeson juga termasuk contoh FromJSON a => FromJSON (Map Text a), yang berarti jika aeson tahu bagaimana mem-parsing sesuatu, ia tahu bagaimana mengurai sesuatu itu.

Anda dapat menentukan tipe data sementara yang menyerupai nilai dalam dikte, kemudian gunakan turunan Peta untuk menentukan FromJSON PersonList, dimana 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
2017-09-06 09:37



Jika Anda mengaktifkan FlexibleInstances, Anda dapat membuat instance untuk [Person]. Anda dapat menguraikan objek Anda Map Text Value lalu parse setiap elemen di peta:

{-# 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
2017-09-06 09:59



Jawaban mniip mengkonversi JSON Object ke a Map, yang mengarah ke daftar hasil yang diurutkan berdasarkan ID. Jika Anda tidak membutuhkan hasil yang disortir dengan cara itu, mungkin lebih baik menggunakan pendekatan yang lebih langsung untuk mempercepat pekerjaan. Khususnya, sebuah Object sebenarnya hanya a HashMap Text Value, jadi kita bisa menggunakannya HashMap operasi untuk bekerja dengannya.

Perhatikan bahwa saya mengganti nama id lapangan ke ident, karena sebagian besar pemrogram Haskell akan menganggap itu id mengacu pada fungsi identitas di Prelude atau ke panah identitas yang lebih umum di 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's 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

Perhatikan bahwa, suka Jawaban Alexander VoidEx Ruchkin, ini mengurutkan konversi dari PersonInfo untuk Person secara eksplisit di dalam Parser monad. Oleh karena itu akan mudah untuk memodifikasinya untuk menghasilkan kesalahan parse jika Person gagal semacam validasi tingkat tinggi. Jawaban Alexander juga menunjukkan kegunaan dari withObject combinator, yang akan saya gunakan jika saya tahu itu ada.


2
2017-09-06 16:08