Haskell中用Aeson.TH为求和类型Declaration派生JSON序列化/反序列化器
没问题,我来一步步教你用Data.Aeson.TH给你的Declaration求和类型派生JSON序列化和反序列化实例,从基础用法到常见定制需求都给你讲清楚:
首先确保你的项目依赖里有aeson和template-haskell包(在cabal或stack配置里加上),然后在代码里开启模板Haskell扩展并导入必要模块:
{-# LANGUAGE TemplateHaskell #-} import Data.Aeson.TH import Data.Maybe (Maybe) -- 别忘了导入你定义的 Constructor、Arg、Typ 类型,不然派生会报错!
最直接的方式就是用deriveJSON函数搭配默认配置,它会自动为求和类型生成带标识tag的JSON结构——每个构造器的JSON会包含一个tag字段(值为构造器名,比如"IndDecl"),再加上对应的记录字段:
deriveJSON defaultOptions ''Declaration
举个例子,IndDecl构造器生成的JSON大概长这样:
{ "tag": "IndDecl", "what": "inductive", "name": "Nat", "argnames": ["a"], "constructors": [] }
默认生成的结构可能不符合你的需求,比如想改tag字段名、调整字段格式、省略空值等,这时候可以通过修改Options来定制:
3.1 自定义构造器的tag字段名
比如不想用默认的"tag",改成"decl_type":
deriveJSON defaultOptions { constructorTagModifier = const "decl_type" } ''Declaration
如果想把构造器名改成小写(比如"inddecl"),可以用字符转换函数:
import Data.Char (toLower) deriveJSON defaultOptions { constructorTagModifier = map toLower } ''Declaration
3.2 调整记录字段名
如果你的字段名需要格式转换(比如从驼峰式改下划线,或者反过来),用fieldLabelModifier。比如把字段名改成全小写(不过你的字段已经是小写了,这里举个通用例子):
-- 假设字段名是驼峰式,比如 argNames,转成 argnames deriveJSON defaultOptions { fieldLabelModifier = map toLower } ''Declaration
3.3 省略空值字段
默认情况下,Maybe类型的字段如果是Nothing,会输出null。如果想直接省略这些字段,设置omitNothingFields = True:
deriveJSON defaultOptions { omitNothingFields = True } ''Declaration
比如argnames为Nothing时,JSON里就不会出现这个字段,更简洁。
3.4 自定义构造器的tag值
如果不想用构造器原名作为tag值(比如把IndDecl改成"inductive"),可以写个匹配函数:
deriveJSON defaultOptions { constructorTagModifier = \case "IndDecl" -> "inductive" "TypeDecl" -> "type_def" "FixDecl" -> "fixity_rule" "TermDecl" -> "term_def" other -> other -- 其他构造器保持原名 } ''Declaration
你的Declaration里用到了Constructor、Arg、Typ这些类型,必须给它们也派生JSON实例,否则deriveJSON会报错。比如:
-- 假设你的 Constructor 是这样的记录类型 data Constructor = Constructor { conName :: String, conArgs :: [Arg] } deriving (Show, Eq) deriveJSON defaultOptions ''Constructor -- 同理处理 Arg 和 Typ data Arg = Arg String deriving (Show, Eq) deriveJSON defaultOptions ''Arg data Typ = Typ String deriving (Show, Eq) deriveJSON defaultOptions ''Typ
写个小测试验证一下是否正常工作:
import Data.Aeson (encode, decode) import qualified Data.ByteString.Lazy.Char8 as BSL testDecl :: Declaration testDecl = IndDecl { what = "inductive", name = "Nat", argnames = Just ["n"], constructors = [Constructor "Zero" [], Constructor "Succ" [Arg "n"]] } main :: IO () main = do let jsonOutput = encode testDecl BSL.putStrLn jsonOutput -- 打印序列化后的JSON case decode jsonOutput of Just parsedDecl -> print $ parsedDecl == testDecl -- 应该输出 True Nothing -> putStrLn "反序列化失败!"
- 报错“找不到类型 Constructor/Arg/Typ”:检查这些类型是否已经定义,并且在当前模块可见(导入或同模块定义)。
- 反序列化失败:核对JSON结构是否和派生的实例匹配——比如tag字段名是否正确、字段名大小写是否一致、嵌套类型的JSON格式是否正确。
- 字段名不匹配:检查
fieldLabelModifier的设置,有没有格式转换错误(比如大小写、下划线处理)。
内容的提问来源于stack exchange,提问作者rausted

