Aeson 中的单标签构造函数
Single tag constructors in Aeson
我有这样的数据类型:
data A = A T.Text deriving (Generic, Show)
instance A.ToJSON A
如果我对它使用 A.encode
:
A.encode $ A "foobar" -- "foobar"
然后我用singleTagConstructors
就可以了:
instance A.ToJSON A where
toEncoding a = A.genericToEncoding $ A.defaultOptions { A.tagSingleConstructors = True }
A.encode $ A "foobarquux" -- "{tag: A, contents: foobarquux}"
在某些时候我做了另一种数据类型:
newtype Wrapper a = Wrapper
{ unWrap :: a
} deriving (Show)
instance A.ToJSON a => A.ToJSON (Wrapper a) where
toJSON w = A.object [ "wrapped" A..= unWrap w ]
这是我感到困惑的部分:
A.encode $ Wrapper $ A "foobar" -- "{wrapped: foobar}"
如何得到这样的结果?
"{wrapped: {tag: A, contents: foobarquux}}"
要直接回答问题,您始终可以使用 tagSingleConstructors = False
实现 Wrapper
实例,如下所示:
instance Generic a => A.ToJSON (Wrapper a) where
toJSON w = A.object [ "wrapped" A..= encA (unWrap w) ]
where
encA = A.genericToEncoding $ A.defaultOptions { A.tagSingleConstructors = False }
但我不明白你为什么要那样做。
如果您控制 API,则不需要 tag
字段:包装值的预期类型已经静态已知,因此 tag
不会有帮助。
如果您不控制 API,我建议非常明确地表示它,例如作为与 API 形状完全匹配的记录。否则,您 运行 有可能通过在代码库的远程部分进行不相关的更改而意外破坏 API。
问题在于您如何实现自定义 ToJSON
实例。
instance A.ToJSON A where
toEncoding a = A.genericToEncoding $ A.defaultOptions { A.tagSingleConstructors = True }
因为你没有实现 toJSON
直接使用类型类定义的默认实现。
class ToJSON a where -- excerpt from Data.Aeson.Types.ToJSON
-- | Convert a Haskell value to a JSON-friendly intermediate type.
toJSON :: a -> Value
default toJSON :: (Generic a, GToJSON' Value Zero (Rep a)) => a -> Value
toJSON = genericToJSON defaultOptions
实际上,您有以下实例:
instance A.ToJSON A where
toJSON = genericToJSON defaultOptions
toEncoding a = A.genericToEncoding $ A.defaultOptions { A.tagSingleConstructors = True }
虽然 toEncoding
使用预期的标记编码,但 toJSON
使用默认编码(不标记单个构造函数)。这种不一致是造成混乱的根本原因。后来,在 wrapper 的 ToJSON
实例中使用了 .=
运算符。在内部,它使用 toJSON
而不是 toEncoding
:
class KeyValue kv where
(.=) :: ToJSON v => Text -> v -> kv
infixr 8 .=
instance KeyValue Pair where
name .= value = (name, toJSON value)
作为解决方案,您应该只定义 toJSON
并保留默认的 toEncoding
实现(使用 toJSON
),或者同时实现两者。
我有这样的数据类型:
data A = A T.Text deriving (Generic, Show)
instance A.ToJSON A
如果我对它使用 A.encode
:
A.encode $ A "foobar" -- "foobar"
然后我用singleTagConstructors
就可以了:
instance A.ToJSON A where
toEncoding a = A.genericToEncoding $ A.defaultOptions { A.tagSingleConstructors = True }
A.encode $ A "foobarquux" -- "{tag: A, contents: foobarquux}"
在某些时候我做了另一种数据类型:
newtype Wrapper a = Wrapper
{ unWrap :: a
} deriving (Show)
instance A.ToJSON a => A.ToJSON (Wrapper a) where
toJSON w = A.object [ "wrapped" A..= unWrap w ]
这是我感到困惑的部分:
A.encode $ Wrapper $ A "foobar" -- "{wrapped: foobar}"
如何得到这样的结果?
"{wrapped: {tag: A, contents: foobarquux}}"
要直接回答问题,您始终可以使用 tagSingleConstructors = False
实现 Wrapper
实例,如下所示:
instance Generic a => A.ToJSON (Wrapper a) where
toJSON w = A.object [ "wrapped" A..= encA (unWrap w) ]
where
encA = A.genericToEncoding $ A.defaultOptions { A.tagSingleConstructors = False }
但我不明白你为什么要那样做。
如果您控制 API,则不需要 tag
字段:包装值的预期类型已经静态已知,因此 tag
不会有帮助。
如果您不控制 API,我建议非常明确地表示它,例如作为与 API 形状完全匹配的记录。否则,您 运行 有可能通过在代码库的远程部分进行不相关的更改而意外破坏 API。
问题在于您如何实现自定义 ToJSON
实例。
instance A.ToJSON A where
toEncoding a = A.genericToEncoding $ A.defaultOptions { A.tagSingleConstructors = True }
因为你没有实现 toJSON
直接使用类型类定义的默认实现。
class ToJSON a where -- excerpt from Data.Aeson.Types.ToJSON
-- | Convert a Haskell value to a JSON-friendly intermediate type.
toJSON :: a -> Value
default toJSON :: (Generic a, GToJSON' Value Zero (Rep a)) => a -> Value
toJSON = genericToJSON defaultOptions
实际上,您有以下实例:
instance A.ToJSON A where
toJSON = genericToJSON defaultOptions
toEncoding a = A.genericToEncoding $ A.defaultOptions { A.tagSingleConstructors = True }
虽然 toEncoding
使用预期的标记编码,但 toJSON
使用默认编码(不标记单个构造函数)。这种不一致是造成混乱的根本原因。后来,在 wrapper 的 ToJSON
实例中使用了 .=
运算符。在内部,它使用 toJSON
而不是 toEncoding
:
class KeyValue kv where
(.=) :: ToJSON v => Text -> v -> kv
infixr 8 .=
instance KeyValue Pair where
name .= value = (name, toJSON value)
作为解决方案,您应该只定义 toJSON
并保留默认的 toEncoding
实现(使用 toJSON
),或者同时实现两者。