Python 的 json.dumps(或)加载到 haskell-aeson?
Python's json.dumps (or) loads in haskell-aeson?
在用于对象 serializing/deserializing 的 Aeson library 中,我看到函数 FromJSON
和 ToJSON
声明为实例。代码是,
data Coord = Coord { x :: Double, y :: Double }
deriving (Show)
instance ToJSON Coord where
toJSON (Coord xV yV) = object [ "x" .= xV,
"y" .= yV ]
我的问题是,
- 为什么作者只用一种方法创建
ToJSON/FromJSON
个实例? toJSON
/parseJSON
不能单独写成一个函数吗?
- 在Python中,只需
json.loads/json.dumps
即可处理任何一种object/json-string。为什么 haskell 用户需要为他序列化的每个对象编写所有这些额外的代码?
- 对于具有多个层次结构的复合对象,如
{"a":
{"b":
{
"c":1
}
}
}
, 我们是否需要在每个级别创建多个 data
和 instance
?
Why does the author create ToJSON
/FromJSON
instances with just one method? Can't toJSON
/parseJSON
be written as a function on its own?
你误解了很多东西,所以让我澄清一下。 ToJSON
和 FromJSON
不是函数。这些是 类型类 。类型类是一种在 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
应用于 String
、Int
或 Double
的值,它会被分派到正确的实现:
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 对象。这样就不必定义新记录了。
在用于对象 serializing/deserializing 的 Aeson library 中,我看到函数 FromJSON
和 ToJSON
声明为实例。代码是,
data Coord = Coord { x :: Double, y :: Double }
deriving (Show)
instance ToJSON Coord where
toJSON (Coord xV yV) = object [ "x" .= xV,
"y" .= yV ]
我的问题是,
- 为什么作者只用一种方法创建
ToJSON/FromJSON
个实例?toJSON
/parseJSON
不能单独写成一个函数吗? - 在Python中,只需
json.loads/json.dumps
即可处理任何一种object/json-string。为什么 haskell 用户需要为他序列化的每个对象编写所有这些额外的代码? - 对于具有多个层次结构的复合对象,如
{"a":
{"b":
{
"c":1
}
}
}
, 我们是否需要在每个级别创建多个 data
和 instance
?
Why does the author create
ToJSON
/FromJSON
instances with just one method? Can'ttoJSON
/parseJSON
be written as a function on its own?
你误解了很多东西,所以让我澄清一下。 ToJSON
和 FromJSON
不是函数。这些是 类型类 。类型类是一种在 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
应用于 String
、Int
或 Double
的值,它会被分派到正确的实现:
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 对象。这样就不必定义新记录了。