假设我们有一些数据类
{-# 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)
然后我们尝试如下解码:
*Main > decode $ pack "{}" :: Maybe (Bar String)
Just (Foo {payload = Nothing})
*Main > decode $ pack "{}" :: Maybe (Foo (Maybe String))
Nothing
那么为什么我们不能在上一次尝试中解码JSON?数据类似乎是相同的,并且它们与toJSON
的工作方式相同:
*Main > toJSON $ Foo (Nothing :: Maybe String)
Object (fromList [("payload",Null)])
*Main > toJSON $ Bar (Nothing :: Maybe String)
Object (fromList [("payload",Null)])
答案 0 :(得分:5)
已更新:,底部有一个简单的解决方案。
这令人困惑,但是它或多或少地按设计工作。您可以尝试将其作为aeson
问题提交,但是我怀疑它会因为“无法解决”而被关闭。
正在发生的事情是,为FromJSON (Bar a)
生成的通用实例等效于:
instance FromJSON a => FromJSON (Bar a) where
parseJSON = withObject "Bar" $ \v -> Bar
<$> v .:? "payload"
请注意由于(.:?)
中的Maybe a
字段而产生的Bar
运算符的使用。在具有Maybe
和非Maybe
字段混合的结构中,将有(.:?)
和(.:)
运算符的相应混合。
请注意,此实例将为所有可能的a
一劳永逸地生成。之所以具有多态性,是因为(.:?)
实现可以分派给实例约束提供的parseJSON
字典中的FromJSON a
方法。另请注意,我们可以使用(.:?)
的唯一原因是,在编译时就知道,对于所有可能的类型a
,payload
对象中的字段Bar
的类型为{ {1}},因此使用Maybe a
运算符将进行类型检查。
现在,考虑为(.:?)
生成的实例。这将等效于:
FromJSON (Foo a)
除了使用instance FromJSON a => FromJSON (Foo a) where
parseJSON = withObject "Foo" $ \v -> Foo
<$> v .: "payload"
运算符外,它与上面的Bar a
实例完全相似。同样,它在编译时只有一个实现,可以通过分派到(.:)
字典中的a
来为每个可能的parseJSON
工作。由于通用FromJSON a
和(.:?)
不能统一,因此该实例无法使用a
运算符,也无法以某种方式“检查”类型{{1 }},无论是在编译时还是在运行时,都可以查看它是否为Maybe t
,其原因与您无法编写类型为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)
,并且将此值编码为JSON时,null字段的删除会统一发生。
这在Value
和> toJSON (Foo (Nothing :: Maybe String))
Object (fromList [("payload",Null)])
> toJSON (Bar (Nothing :: Maybe String))
Object (fromList [("payload",Null)])
实例中导致了不幸的不对称,但这就是正在发生的事情。
我只是意识到我忘了分享修复它的简单解决方案。只需为FromJSON
定义两个通用实例,一个重叠的实例即可处理ToJSON
,另一个实例用于其他类型:
Foo