如何使用 Aeson 将 Haskell ADT 序列化为整洁的 JSON?
How to serialise Haskell ADTs as tidy JSON, using 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)
输出为:
{"tag":"AttachMsg","contents":{"tel":"+447890"}}
{"attach":{"tel":"+447890"},"fix":null}
我正在寻找的输出是:
{"attach":{"tel":"+447890"}}
但来自 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)
得到了我想要的答案:{"attach":{"tel":"+447890"}}
.
@bheklilr 有没有办法使用 Attach 的(已经定义的)序列化,而不是再次定义它?
我尝试了一些无意义的语法,但可以理解它不能编译:
instance ToJSON MsgIn3 where
toJSON (AttachMsg3 (Attach tel)) = object ["attach" .= (toJSON :: Attach)]
您可以让 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"}}
使用自定义选项而不是默认选项。您可以使用 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}
由于模板 Haskell,此处需要一个单独的模块。可能有一种方法可以更好地定义 myConstructorTag 和 myOptions 来满足 TH 的需求,但我完全不知道该怎么做。
我花了一些时间研究 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)
输出为:
{"tag":"AttachMsg","contents":{"tel":"+447890"}}
{"attach":{"tel":"+447890"},"fix":null}
我正在寻找的输出是:
{"attach":{"tel":"+447890"}}
但来自 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)
得到了我想要的答案:{"attach":{"tel":"+447890"}}
.
@bheklilr 有没有办法使用 Attach 的(已经定义的)序列化,而不是再次定义它?
我尝试了一些无意义的语法,但可以理解它不能编译:
instance ToJSON MsgIn3 where
toJSON (AttachMsg3 (Attach tel)) = object ["attach" .= (toJSON :: Attach)]
您可以让 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"}}
使用自定义选项而不是默认选项。您可以使用 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}
由于模板 Haskell,此处需要一个单独的模块。可能有一种方法可以更好地定义 myConstructorTag 和 myOptions 来满足 TH 的需求,但我完全不知道该怎么做。