Haskell/Aseon:输出JSON作为一个对象

Haskell/Aseon: output JSON as one object

我有以下 Haskell 代码,它在 JSON 中编码数据类型 User 的列表并将其打印到标准输出:

{-# 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?

How will I have to change toJSON in order to print the desired encoded JSON?

您无法为 User 更改 toJSON 以打印所需的编码 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 将对象存储为 HashMapTextValueHashMap 被编码为 JSON 对象。因此,给定解决方案的想法是将每个用户转换为单例 HashMap,然后联合所有 HashMap

注意:它会从列表中删除重复userId的用户。