Python 的 json.dumps(或)加载到 haskell-aeson?

Python's json.dumps (or) loads in haskell-aeson?

在用于对象 serializing/deserializing 的 Aeson library 中,我看到函数 FromJSONToJSON 声明为实例。代码是,

data Coord = Coord { x :: Double, y :: Double }
         deriving (Show)
instance ToJSON Coord where
toJSON (Coord xV yV) = object [ "x" .= xV,
                              "y" .= yV ]

我的问题是,

{"a": {"b": { "c":1 } } } , 我们是否需要在每个级别创建多个 datainstance

Why does the author create ToJSON/FromJSON instances with just one method? Can't toJSON/parseJSON be written as a function on its own?

你误解了很多东西,所以让我澄清一下。 ToJSONFromJSON 不是函数。这些是 类型类 。类型类是一种在 Haskell.

中编写多态代码的方法

在这里,我将解释一个非常简单和不完整的 json 序列化定义。首先我们声明一个类型类:

class ToJSON a where
    toJSON :: a -> String

这个语句基本上是说:"If a is an instance of typeclass ToJSON, then we can use function toJSON to serialize a into a JSON string".

定义类型类后,可以为各种简单类型实现实例

instance ToJSON String where
    toJSON s = s

instance ToJSON Int where
    toJSON n = show n

instance ToJSON Double where
    toJSON n = show n

定义这些简单的实现后,您可以将 toJSON 应用于 StringIntDouble 的值,它会被分派到正确的实现:

toJSON "hello"         -----> "hello"
toJSON (5 :: Int)      -----> "5"
toJSON (5.5 :: Double) -----> "5.5"

为了更进一步,我们需要一种编码 JSON 集合的方法。让我们从列表开始。我们想表达的是,如果有一个值a可以序列化为JSON,那么这样一个值的列表也可以序列化为JSON.

--        ,-- value 'a' can be serialized into JSON    
--       ,--------,    
instance (ToJSON a) => ToJSON [a] where
--                     ``````````-- A list of such values can also be serialized    
    -- | Here is how serialization can be performed
    toJSON as = "[" ++ (intercalate ", " $ map toJSON as) ++ "]"

我们对列表中的每个值进行序列化,用“,”分隔并括在方括号中。请注意,对 toJSON 的递归调用被分派到正确的实现。

现在我们可以在列表上使用 toJSON

toJSON [1,2,3,4] -----> "[1, 2, 3, 4]"

您可以更进一步,尝试实现整个 JSON 语法。您的下一步可能是地图。我会把它留作练习。

我的意思是解释当您编写 instance ToJSON Coord ... 时,您只是提供了一种将 Coord 序列化为 JSON 的方法。这使您能够序列化 Coords 的列表、Coords 的映射以及许多其他内容。如果没有类型类,这是不可能的。


In Python, one just does json.loads/json.dumps to handle any kind of object/json-string. Why does the haskell user need to write all these extra code for every object that he seralizes?

重要的一点是 Python 的 json.loads 不会将 json 反序列化到您的对象中。它会将其反序列化为一个内置结构,该结构可能等同于您的对象。您可以在 Haskell 中使用模板 haskell 做同样的事情,它会为您声明 ToJSON/FromJSON 个实例。或者,您可以将 JSON 转储到键值 Map 中并对其进行操作。

但是,编写额外的代码(或自动生成它)会给您带来很多好处,这些好处可以用 "type safety".

来概括

For composite objects with multiple hierarchies like ..., do we need to create multiple data and instance at each level?

不,你不知道。如果您链接了将数字转换为此类结构的实例的结构,反之亦然,则大致如下所示:

-- | Just a wrapper for the number which must be stored in a nested structure
newtype NestedStructure = NestedStructure Int

instance ToJSON NestedStructure where
    toJSON (NestedStructure n) =
        object ["a" .= object ["b" .= object ["c" .= n]]]

instance FromJSON NestedStructure where
    fromJSON (Object o) = NestedStructure <$> ((o .: "a") >>= (.: "b")
                                                          >>= (.: "c"))
    fromJSON _ = mzero

In Python, one just does json.loads/json.dumps to handle any kind of object/json-string. Why does the haskell user need to write all these extra code for every object that he seralizes?

避免这种情况的一种方法是使用 deriving mechanism in combination with GHC.Generics.

"deriving" 机制自动为您生成类型类实例,避免样板。例如:

{-# LANGUAGE DeriveGeneric #-}

import GHC.Generics

data VDPServer = VDPServer 
    { vdpHost :: String
    , vdpPort :: Int
    , vdpLogin :: String
    , vdpPassword :: String
    , vdpDatabase :: String 
    }
    deriving Generic

instance FromJSON VDPServer

instance ToJSON VDPServer

这在文档的 Type Conversion 部分中有描述。

您可以使用 Options 类型自定义生成实例的方式:

aesonOptions :: Options
aesonOptions = defaultOptions 
    { sumEncoding = ObjectWithSingleField 
    , fieldLabelModifier = tail
    , omitNothingFields = True
    }

instance FromJSON VDPServer where
    parseJSON = genericParseJSON aesonOptions

instance ToJSON VDPServer where
    toJSON = genericToJSON aesonOptions

有时,在处理复杂的预先存在的 JSON 模式时,这种方法效果不佳,必须回退到手动定义解析器。但对于更简单的情况,它避免了很多样板文件。

do we need to create multiple data and instance at each level?

所有记录字段必须有自己的 FromJSON/ToJSON 个实例。许多常见类型(元组、列表、映射、字符串...)已经有这样的实例,请参阅文档中 FromJSON 的实例列表。但如果没有,您将不得不定义它们(也许再次使用 Generic 技巧)。

In Python, one just does json.loads/json.dumps to handle any kind of object/json-string. Why does the haskell user need to write all these extra code for every object that he seralizes?

Haskell 相当于将 JSON 文件反序列化为映射、列表和原始类型的组合是读取 Value 对象。这样就不必定义新记录了。