假设我们有一些数据类
{-# LANGUAGE DeriveGeneric, DuplicateRecordFields #-}
import Data.Aeson
import Data.ByteString.Lazy.Char8
import GHC.Generics
data Foo a = Foo { payload :: a }
deriving (Show, Generic)
instance ToJSON a => ToJSON (Foo a)
instance FromJSON a => FromJSON (Foo a)
data Bar a = Bar { payload :: Maybe a }
deriving (Show, Generic)
instance ToJSON a => ToJSON (Bar a)
instance FromJSON a => FromJSON (Bar a)
Run Code Online (Sandbox Code Playgroud)
然后我们尝试解码如下:
*Main > decode $ pack "{}" :: Maybe (Bar String)
Just (Foo {payload = Nothing})
*Main > decode $ pack "{}" :: Maybe (Foo (Maybe String))
Nothing
Run Code Online (Sandbox Code Playgroud)
那么,为什么不能在上一次尝试中解码JSON?数据类似乎是相同的,并且它们的使用方式都相同toJSON:
*Main > toJSON $ Foo (Nothing :: Maybe String)
Object (fromList [("payload",Null)])
*Main > toJSON $ Bar (Nothing :: Maybe String)
Object (fromList [("payload",Null)])
Run Code Online (Sandbox Code Playgroud)
更新:底部有一个简单的解决方案。
这很令人困惑,但是它或多或少地按设计工作。您可以尝试将其作为aeson问题提交,但我怀疑它将由于“无法解决”而被关闭。
发生的是,为生成的通用实例FromJSON (Bar a)等效于:
instance FromJSON a => FromJSON (Bar a) where
parseJSON = withObject "Bar" $ \v -> Bar
<$> v .:? "payload"
Run Code Online (Sandbox Code Playgroud)
请注意,(.:?)由于中存在Maybe a字段,因此使用了生成的运算符Bar。在具有Maybe和非Maybe字段混合的结构中,将存在(.:?)和(.:)运算符的相应混合。
请注意,此实例将为所有可能的情况一劳永逸地生成a。它具有多态性的原因是,(.:?)实现可以分派给实例约束提供parseJSON的FromJSON a字典中的方法。另外请注意,我们可以使用的唯一原因(.:?)是,它在编译时,对于所有可能的已知类型的a,外地payload的Bar对象有型Maybe a,因此使用的(.:?)运营商将进行类型检查。
现在,考虑为生成的实例FromJSON (Foo a)。这等效于:
instance FromJSON a => FromJSON (Foo a) where
parseJSON = withObject "Foo" $ \v -> Foo
<$> v .: "payload"
Run Code Online (Sandbox Code Playgroud)
Bar a除了使用(.:)运算符之外,它与上面的实例完全相似。同样,它在编译时只有一个实现,可以a通过parseJSON在FromJSON a字典中分派到来实现所有可能。(.:?)由于通用a且Maybe t无法统一,因此该实例无法使用运算符,并且无论如何,它都无法以某种方式“检查”类型a(无论是在编译时还是在运行时)以查看其是否是a Maybe,原因是您不能使用a -> a除身份以外的任何类型编写总多态函数。
因此,该Foo a实例不能使该payload字段成为可选字段!取而代之的是,它必须被payload视为强制性的,并且在用于解析时将其视为Foo (Maybe String)对FromJSON t => FromJSON (Maybe t)实例的调度(这允许null但以其他方式调度至FromJSON String实例)。
现在,为什么它似乎可以正常工作ToJSON?那么,对于这两个实例中ToJSON (Foo a),并ToJSON (Bar a)生成相同的排序(单态)的Value表示:
> toJSON (Foo (Nothing :: Maybe String))
Object (fromList [("payload",Null)])
> toJSON (Bar (Nothing :: Maybe String))
Object (fromList [("payload",Null)])
Run Code Online (Sandbox Code Playgroud)
当此值编码为JSON时,null字段的删除会统一进行。
不幸的是FromJSON,这导致了和ToJSON实例的不对称,但这就是正在发生的事情。
我只是意识到我忘了分享修复它的简单解决方案。只需为定义两个通用实例Foo,一个要处理的重叠实例,Maybes另一个为其他类型的实例:
instance {-# OVERLAPPING #-} FromJSON a => FromJSON (Foo (Maybe a))
instance FromJSON a => FromJSON (Foo a)
Run Code Online (Sandbox Code Playgroud)