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

时间:2015-07-15 20:04:55

标签: json haskell serialization algebraic-data-types aeson

我花了一些时间和Aeson一起玩,但我不能很好地将代数数据类型序列化。

我尝试的是:

@cache_page(60 * 60 * 24)
def myview()

输出结果为:

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)

我正在寻找的输出是:

{"tag":"AttachMsg","contents":{"tel":"+447890"}}
{"attach":{"tel":"+447890"},"fix":null}

但是来自{"attach":{"tel":"+447890"}} 类型,而不是MsgIn

MsgIn2的输出非常接近,但它有一个明确的MsgIn2。)

有没有办法在Aeson这样做?

更新

我补充说:

null

得到了我想要的答案:instance ToJSON MsgIn3 where toJSON (AttachMsg3 (Attach tel)) = object ["attach" .= object ["tel" .= tel]] ... let reply3 = AttachMsg3 attach BL.putStrLn (encode reply3)

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

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

{"attach":{"tel":"+447890"}}

2 个答案:

答案 0 :(得分: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}

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

答案 1 :(得分:4)

您可以让aeson自动跳过空字段。我通常会与DeriveGeneric扩展名一起执行此操作:

{-# LANGUAGE OverloadedStrings, DeriveGeneric #-}

import Data.Aeson
import Data.Aeson.Types
import qualified Data.ByteString.Lazy.Char8 as BL
import GHC.Generics

data Attach = Attach { tel :: String } deriving (Show, Generic)

data Fix = Fix { lat :: Double, lng :: Double } deriving (Show, Generic)

data Msg = Msg { attach :: Attach, fix :: Maybe Fix } deriving (Show, Generic)

instance ToJSON Attach
instance ToJSON Fix
instance ToJSON Msg where
    toJSON = genericToJSON (defaultOptions { omitNothingFields = True })

main = do
    let attach = Attach "+447890"
        reply  = Msg attach Nothing
    BL.putStrLn (encode reply)

给你:

*Main> main
{"attach":{"tel":"+447890"}}