我可以在Data.Aeson和Data.Yaml之间重用ToJSON和FromJSON实例吗?

时间:2018-04-26 19:26:16

标签: haskell aeson

Data.Aeson和Data.Yaml分别是用于处理JSON和YAML的库,它们具有几乎相同的接口。我为某些类型编写了“基于Aeson”的FromJSONToJSON个实例:

import Data.Aeson

data PropertyValue = Nested Microformat | Flat Text
    deriving (Generic, Show)

instance ToJSON PropertyValue where
    toEncoding = genericToEncoding defaultOptions { sumEncoding = UntaggedValue }

instance FromJSON PropertyValue where
    parseJSON = genericParseJSON defaultOptions { sumEncoding = UntaggedValue }

虽然Aeson使用这些实例,但Yaml似乎忽略了它们。 (具体来说,我相信Yaml会自动生成编码器和解码器,这要归功于我的类型派生Generic。)我试图通过将代码更改为

来使实例与Yaml一起使用。
import Data.Aeson
import qualified Data.Yaml as Y

data PropertyValue = Nested Microformat | Flat Text
    deriving (Generic, Show)

instance ToJSON PropertyValue where
    toEncoding = genericToEncoding defaultOptions { sumEncoding = UntaggedValue }

instance FromJSON PropertyValue where
    parseJSON = genericParseJSON defaultOptions { sumEncoding = UntaggedValue }

instance Y.ToJSON PropertyValue where
    toEncoding = genericToEncoding defaultOptions { sumEncoding = UntaggedValue }

instance Y.FromJSON PropertyValue where
    parseJSON = genericParseJSON defaultOptions { sumEncoding = UntaggedValue }

但GHC抱怨道:

Duplicate instance declarations:
  instance ToJSON PropertyValue
    -- Defined at src/Microformats.hs:54:10
  instance ToJSON PropertyValue
    -- Defined at src/Microformats.hs:60:10

是否无法为碰巧具有相同名称的两个不同类定义实例?或者Yaml的ToJSON实际上和Aeson的相同的类是什么?

更重要的是,是否可以重用我的ToJSONFromJSON实例以避免必须两次编写相同的代码?如果不这样做,是否至少可以在不混淆编译器的情况下编写库ToJSONFromJSON的实例?

1 个答案:

答案 0 :(得分:4)

简短回答ToJSONFromJSON Data.Aeson(和Data.Yaml)类型相同Data.Yaml实际上只执行ToJSON Data.Aeson类型类别的 重新导入

如果我们查看Data.Yaml source code

#if (defined (ghcjs_HOST_OS))
module Data.Yaml {-# WARNING "GHCJS is not supported yet (will break at runtime once called)." #-}
#else
module Data.Yaml
#endif
    ( -- * Types
      Value (..)
    , Parser
    , Object
    , Array
    , ParseException(..)
    , prettyPrintParseException
    , YamlException (..)
    , YamlMark (..)
      -- * Constructors and accessors
    , object
    , array
    , (.=)
    , (.:)
    , (.:?)
    , (.!=)
      -- ** With helpers (since 0.8.23)
    , withObject
    , withText
    , withArray
    , withScientific
    , withBool
      -- * Parsing
    , parseMonad
    , parseEither
    , parseMaybe
      -- * Classes
    , ToJSON (..)
    , FromJSON (..)
      -- * Encoding/decoding
    , encode
    , encodeFile
    , decode
    , decodeFile
      -- ** Better error information
    , decodeEither
    , decodeEither'
    , decodeFileEither
      -- ** More control over decoding
    , decodeHelper
    ) where
#if !MIN_VERSION_base(4,8,0)
import Control.Applicative(())
#endif
import Control.Exception
import Data.Aeson
    ( Value (..), ToJSON (..), FromJSON (..), object
    , (.=) , (.:) , (.:?) , (.!=)
    , Object, Array
    , withObject, withText, withArray, withScientific, withBool
    )
(...)

因此模块会导出ToJSON类型类,但这只是ToJSON导入Data.Aeson的结果,因此类型类实际上是相同的。

因此,程序员方便重新导入(例如,您不必导入Data.Aeson仅用于实现FromJOSN),但事实上您仍然使用相同类型,相同类型所有ToJSONY.ToJSON引用相同的类型类。

由于两者实际上是相同的,因此您无法为同一类型实例化两次相同的类型类,但我们不需要这样做:如果我们为Data.Aeson(或Data.Yaml实现它),这就足够了,因为例如用Data.Yaml(或Data.Aeson)编写的类型类约束将成功。结果是我们不能(至少没有一些技巧)对ToJSON实施Data.Yaml而不是Data.Aeson