使用Aeson解析JSON时,为什么将其放在类型参数中可能会有所不同?

时间:2019-06-11 13:00:41

标签: haskell aeson

假设我们有一些数据类

{-# 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)])

1 个答案:

答案 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方法。另请注意,我们可以使用(.:?)的唯一原因是,在编译时就知道,对于所有可能的类型apayload对象中的字段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 StringToJSON的实例都生成相同类型的(单态)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