JSON 嵌套序列化

JSON nested serialisation

假设有一个数据类型

data V = V { a :: Int, x :: Int, y :: Int }

有通讯员JSON观点

例如V { a = 1, x = 2, y = 3 }需要像

一样序列化
{
  "a": 1,
  "nested": {
    "x": 2,
    "y": 3
  }
}

在那种情况下 ToJSON 实例会是什么样子?


我尝试过的:

instance ToJSON V where
  toEncoding (V a b c) = 
    pairs (  "a" .= a
          <> ("nested" .= pairs ("x" .= x <> "y" .= y))
          )


<interactive>:6:10: error:
    • No instance for (GHC.Generics.Generic V)
        arising from a use of ‘aeson-1.1.1.0:Data.Aeson.Types.ToJSON.$dmtoJSON’
    • In the expression:
        aeson-1.1.1.0:Data.Aeson.Types.ToJSON.$dmtoJSON @V
      In an equation for ‘toJSON’:
          toJSON = aeson-1.1.1.0:Data.Aeson.Types.ToJSON.$dmtoJSON @V
      In the instance declaration for ‘ToJSON V’

<interactive>:6:68: error:
    • No instance for (ToJSON Encoding) arising from a use of ‘.=’
    • In the second argument of ‘(<>)’, namely
        ‘("nested" .= pairs ("x" .= x <> "y" .= y))’
      In the first argument of ‘pairs’, namely
        ‘("a" .= a <> ("nested" .= pairs ("x" .= x <> "y" .= y)))’
      In the expression:
        pairs ("a" .= a <> ("nested" .= pairs ("x" .= x <> "y" .= y)))

<interactive>:6:87: error:
    • No instance for (ToJSON (V -> Int)) arising from a use of ‘.=’
        (maybe you haven't applied a function to enough arguments?)
    • In the first argument of ‘(<>)’, namely ‘"x" .= x’
      In the first argument of ‘pairs’, namely ‘("x" .= x <> "y" .= y)’
      In the second argument of ‘(.=)’, namely
        ‘pairs ("x" .= x <> "y" .= y)’
(0.01 secs,)

实例可能如下所示:

data V = V { a :: Int, x :: Int, y :: Int }

instance ToJSON V where
    toJSON (V a x y) = object
        [ "a" .= a
        , "nested" .= object
            [ "x" .= x
            , "y" .= y ]
        ]

你可以在ghci中测试它:

ghci> import qualified Data.ByteString.Lazy.Char8 as B
ghci> B.putStrLn $ encode (V 1 2 3)
{"nested":{"x":2,"y":3},"a":1}

UPD(关于toEncoding):

您很可能不想定义 toEncoding。此方法具有默认实现,它是使用 toJSON 方法定义的。但是 toJSON 方法没有针对一般情况的实现。它只有 Generic 数据类型的 default 实现。

你的实现几乎没问题,除了它有拼写错误:方法体中的 "x" .= x <> "y" .= y 和模式匹配中的 (V a b c) (因此它使用 x 变量作为函数,你得到了那些令人毛骨悚然的错误).您需要为您的 V 数据类型导出 Generic 才能工作。并且您需要在一个地方使用内部的 pair 函数而不是 .= 。这是完整版:

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveGeneric #-}

import Data.Monoid ((<>))
import GHC.Generics (Generic)
import Data.Aeson (ToJSON (..), pairs, (.=))
import Data.Aeson.Encoding.Internal (pair)

data V = V { a :: Int, x :: Int, y :: Int } deriving (Generic)

instance ToJSON V where
    toEncoding (V a x y) = 
        pairs ("a" .= a <> (pair "nested" $ pairs ("x" .= x <> "y" .= y)))

但要注意可能的不一致:

ghci> encode (V 1 2 3)
"{\"a\":1,\"nested\":{\"x\":2,\"y\":3}}"
ghci> toEncoding (V 1 2 3)
"{\"a\":1,\"nested\":{\"x\":2,\"y\":3}}"
ghci> toJSON (V 1 2 3)
Object (fromList [("a",Number 1.0),("x",Number 2.0),("y",Number 3.0)])