如何使用Aeson将Haskell ADT序列化为整洁的JSON?

fad*_*bee 5 serialization json haskell algebraic-data-types aeson

我花了一些时间和Aeson一起玩,但我无法让代数数据类型很好地序列化.

我试过的是:

data Attach = Attach { tel :: String }
              deriving (Show)
$(deriveJSON defaultOptions ''Attach)

data Fix = Fix { lat :: Double, lng :: Double }
              deriving (Show)
$(deriveJSON defaultOptions ''Fix)

data MsgIn = AttachMsg Attach
           | FixMsg    Fix
           deriving (Show)
$(deriveJSON defaultOptions ''MsgIn)

data MsgIn2 = MsgIn2 { attach :: Maybe Attach, fix :: Maybe Fix }
            deriving (Show)
$(deriveJSON defaultOptions ''MsgIn2)

someFunc :: IO ()
someFunc = do
  let attach = Attach "+447890"
  let reply = AttachMsg attach
  BL.putStrLn (encode reply)
  let reply2 = MsgIn2 (Just attach) Nothing
  BL.putStrLn (encode reply2)
Run Code Online (Sandbox Code Playgroud)

输出是:

{"tag":"AttachMsg","contents":{"tel":"+447890"}}
{"attach":{"tel":"+447890"},"fix":null}
Run Code Online (Sandbox Code Playgroud)

我正在寻找的输出是:

{"attach":{"tel":"+447890"}}
Run Code Online (Sandbox Code Playgroud)

但是从MsgIn类型而不是MsgIn2.

(输出MsgIn2非常接近,但它有明确的null.)

有没有办法在Aeson这样做?


更新:

我补充说:

instance ToJSON MsgIn3 where
  toJSON (AttachMsg3 (Attach tel)) = object ["attach" .= object ["tel" .= tel]]
...
let reply3 = AttachMsg3 attach
BL.putStrLn (encode reply3)
Run Code Online (Sandbox Code Playgroud)

得到了我想要的答案:{"attach":{"tel":"+447890"}}.

@bheklilr有没有办法使用Attach(已经定义的)序列化,而不是再次定义它?

我尝试了一些无意义的语法,但可以理解的是它不能编译:

instance ToJSON MsgIn3 where
  toJSON (AttachMsg3 (Attach tel)) = object ["attach" .= (toJSON :: Attach)] 
Run Code Online (Sandbox Code Playgroud)

gek*_*kio 5

使用自定义选项而不是defaultOptions.你可以通过使用获得正确的结构sumEncoding = ObjectWithSingleField,这将你的第一个例子减少到了{"AttachMsg":{"tel":"+447890"}}.然后,您可以通过使用constructorTagModifier = myConstructorTag和编写一个myConstructorTag根据您的喜好自定义名称的函数来自定义构造函数标记(例如AttachMsg - > attach).

举个例子,你可以通过将它写入一个单独的模块,导入它,然后使用myOptions而不是defaultOptions:来获得你想要的输出:

myConstructorTag :: String -> String
myConstructorTag "AttachMsg" = "attach"
myConstructorTag x = x

myOptions :: Options
myOptions = defaultOptions {sumEncoding = ObjectWithSingleField, constructorTagModifier = myConstructorTag}
Run Code Online (Sandbox Code Playgroud)

由于Template Haskell,这里需要一个单独的模块.可能有一种方法可以更好地定义myConstructorTag和myOptions以满足TH的需求,但我完全不知道如何做到这一点.