用Aeson解析JSON时,为什么Maybe在类型参数中会被区别对待?
When parsing JSON with Aeson, why is Maybe treated differently when it's in a type parameter?
假设我们有一些数据类
{-# 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)])
更新:底部有一个简单的解决方案。
这令人困惑,但它或多或少按设计工作。您可以尝试将其作为 aeson
问题提交,但我怀疑它将作为 "won't fix".
关闭
发生的事情是为 FromJSON (Bar a)
生成的通用实例等同于:
instance FromJSON a => FromJSON (Bar a) where
parseJSON = withObject "Bar" $ \v -> Bar
<$> v .:? "payload"
请注意由于 Bar
中的 Maybe a
字段而生成的 (.:?)
运算符的使用。在混合了 Maybe
和非 Maybe
字段的结构中,相应地混合了 (.:?)
和 (.:)
运算符。
请注意,此实例是为所有可能的 a
一劳永逸地生成的。它是多态的原因是 (.:?)
实现可以分派给实例约束提供的 FromJSON a
字典中的 parseJSON
方法。另请注意,我们可以使用 (.:?)
的唯一原因是在编译时已知对于所有可能的类型 a
,Bar
对象中的字段 payload
具有类型 Maybe a
所以使用 (.:?)
运算符将进行类型检查。
现在,考虑为 FromJSON (Foo a)
生成的实例。这相当于:
instance FromJSON a => FromJSON (Foo a) where
parseJSON = withObject "Foo" $ \v -> Foo
<$> v .: "payload"
它与上面的 Bar a
实例完全相似,只是它使用了 (.:)
运算符。同样,它在编译时有一个单一的实现,通过分派到 FromJSON a
字典中的 parseJSON
来为每个可能的 a
工作。此实例无法使用 (.:?)
运算符,因为一般的 a
和 Maybe t
无法统一,并且它无法以某种方式 "inspect" 类型 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)])
并且当此值编码为 JSON.
时,将统一删除空字段
这导致 FromJSON
和 ToJSON
实例中出现不幸的不对称,但这就是正在发生的事情。
我才意识到我忘了分享修复它的简单解决方案。只需为 Foo
定义两个通用实例,一个重叠实例用于处理 Maybes
,另一个用于其他类型:
instance {-# OVERLAPPING #-} FromJSON a => FromJSON (Foo (Maybe a))
instance FromJSON a => FromJSON (Foo a)
假设我们有一些数据类
{-# 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)])
更新:底部有一个简单的解决方案。
这令人困惑,但它或多或少按设计工作。您可以尝试将其作为 aeson
问题提交,但我怀疑它将作为 "won't fix".
发生的事情是为 FromJSON (Bar a)
生成的通用实例等同于:
instance FromJSON a => FromJSON (Bar a) where
parseJSON = withObject "Bar" $ \v -> Bar
<$> v .:? "payload"
请注意由于 Bar
中的 Maybe a
字段而生成的 (.:?)
运算符的使用。在混合了 Maybe
和非 Maybe
字段的结构中,相应地混合了 (.:?)
和 (.:)
运算符。
请注意,此实例是为所有可能的 a
一劳永逸地生成的。它是多态的原因是 (.:?)
实现可以分派给实例约束提供的 FromJSON a
字典中的 parseJSON
方法。另请注意,我们可以使用 (.:?)
的唯一原因是在编译时已知对于所有可能的类型 a
,Bar
对象中的字段 payload
具有类型 Maybe a
所以使用 (.:?)
运算符将进行类型检查。
现在,考虑为 FromJSON (Foo a)
生成的实例。这相当于:
instance FromJSON a => FromJSON (Foo a) where
parseJSON = withObject "Foo" $ \v -> Foo
<$> v .: "payload"
它与上面的 Bar a
实例完全相似,只是它使用了 (.:)
运算符。同样,它在编译时有一个单一的实现,通过分派到 FromJSON a
字典中的 parseJSON
来为每个可能的 a
工作。此实例无法使用 (.:?)
运算符,因为一般的 a
和 Maybe t
无法统一,并且它无法以某种方式 "inspect" 类型 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)])
并且当此值编码为 JSON.
时,将统一删除空字段这导致 FromJSON
和 ToJSON
实例中出现不幸的不对称,但这就是正在发生的事情。
我才意识到我忘了分享修复它的简单解决方案。只需为 Foo
定义两个通用实例,一个重叠实例用于处理 Maybes
,另一个用于其他类型:
instance {-# OVERLAPPING #-} FromJSON a => FromJSON (Foo (Maybe a))
instance FromJSON a => FromJSON (Foo a)