Haskell/Aseon: вывод JSON как одного объекта

У меня есть следующий код Haskell, который кодирует список типов данных User в JSON и выводит его на стандартный вывод:

{-# LANGUAGE OverloadedStrings #-}

module Main where

import Data.Aeson
import Data.Text
import qualified Data.ByteString.Lazy.Char8 as B

data User = User
    { id :: String
    , name :: String
    , address :: String
    } deriving (Show)

instance ToJSON User where
    toJSON (User id name address) = object
        [ pack id .= object
            [ "name" .= name
            , "address" .= address
            ]
        ]

users :: [User]
users = [ User "user 1" "name of user 1" "address of user 1"
        , User "user 2" "name of user 2" "address of user 2"
        ] 

main :: IO ()
main = B.putStrLn $ encode users

На данный момент код выдает следующий вывод:

[
  {
    "user 1": {
      "address": "address of user 1",
      "name": "name of user 1"
    }
  },
  {
    "user 2": {
      "address": "address of user 2",
      "name": "name of user 2"
    }
  }
]

Однако я хотел бы вывести следующую структуру JSON (соединяющую два внутренних объекта):

{
  "user 1": {
    "name": "name of user 1",
    "address": "address of user 1"
  },
  "user 2": {
    "name": "name of user 2",
    "address": "address of user 2"
  }
}

Как я должен буду изменить toJSON для того, чтобы напечатать нужный кодированный JSON?

1 ответ

Решение

Как я должен буду изменить toJSON, чтобы напечатать желаемый закодированный JSON?

Вы ничего не можете сделать, чтобы изменить toJSON за User распечатать желаемый закодированный JSON. Проблема не в User кодировка, но в списке кодирования. Простой список, просто закодированный как массив JSON. И массив JSON не имеет значений (поэтому вы не можете иметь ["user 1":{...}] таким образом, каждый объект обернут в {}). Эта проблема может быть решена разными способами. Одним из самых простых решений является написание собственного кодировщика для списка User, Вот как это выглядит:

import qualified Data.HashMap.Strict as HM

usersEncode :: [User] -> Object
usersEncode = HM.unions . map (\(Object user) -> user) . map toJSON

Адн тогда в main Вы можете назвать это так:

main = B.putStrLn $ encode $ usersEncode users

И это дает вам желаемый результат.

Хитрость в том, что aeson хранит объекты как HashMap от Text в Value, А также HashMap кодируется как объект JSON. Таким образом, идея данного решения состоит в том, чтобы преобразовать каждого пользователя в синглтон. HashMap а затем объединить все HashMaps.

Примечание: это удалит пользователей с дублированием userId из списка.

Другие вопросы по тегам