在用于对象序列化/反序列化的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
不能单独编写为函数吗?json.loads/json.dumps
来处理任何类型的对象/ json-string。为什么haskell用户需要为他认为的每个对象编写所有这些额外的代码? {"a":
{"b":
{
"c":1
}
}
}
,我们是否需要在每个级别创建多个data
和instance
?
答案 0 :(得分:6)
为什么作者只用一种方法创建
ToJSON
/FromJSON
个实例?toJSON
/parseJSON
不能单独编写为函数吗?
你误解了很多事情,所以让我清楚一点。 ToJSON
和FromJSON
不是函数。这些是类型。类型类是在Haskell中编写多态代码的一种方法。
这里我将解释json序列化的一个非常简化和不完整的定义。首先我们声明一个类型类:
class ToJSON a where
toJSON :: a -> String
此语句基本上说:“如果a
是类型类ToJSON
的实例,那么我们可以使用函数toJSON
将a
序列化为JSON字符串”。< / p>
当定义类型类时,可以为各种简单类型实现它的实例:
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
的值,并将其分派到正确的实现:< / p>
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
的地图以及许多其他内容。没有类型类,这是不可能的。
在Python中,只需要json.loads / json.dump来处理任何类型的对象/ json-string。为什么haskell用户需要为他认可的每个对象编写所有这些额外的代码?
重要的一点是,Python的json.loads
不会将json反序列化为您的对象。它会将其反序列化为可能与您的对象等效的内置结构。您可以使用模板haskell在Haskell中执行相同的操作,该模板将为您声明ToJSON
/ FromJSON
个实例。或者,您可以将JSON转储到键值Map中并对其进行操作。
然而,编写额外的代码(或自动生成代码)会给你带来很多好处,可以用“类型安全”来概括。
对于具有多个层次结构的复合对象,例如......,我们是否需要在每个级别创建多个数据和实例?
不,不。如果您将链接了将数字转换为此类结构的实例的结构反之亦然,则看起来大致如下所示:
-- | 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
答案 1 :(得分:0)
在Python中,只需要json.loads / json.dump来处理任何类型的 对象/ JSON串。为什么haskell用户需要编写所有这些 他认为每个对象的额外代码?
避免这种情况的一种方法是将deriving机制与GHC.Generics结合使用。
“derived”机制自动为您生成类型类实例,避免使用样板。例如:
{-# 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模式时,这种方法不能很好地工作,并且必须回退到手动定义解析器。但对于更简单的情况,它可以避免大量的样板。
我们是否需要在每个级别创建多个数据和实例?
所有记录字段都必须有自己的FromJSON
/ ToJSON
个实例。许多常见类型(元组,列表,映射,字符串......)已经有这样的实例,请参阅文档中FromJSON的实例列表。但如果没有,你将不得不定义它们(可能再次使用Generic
技巧)。
在Python中,只需要json.loads / json.dump来处理任何类型的 对象/ JSON串。为什么haskell用户需要编写所有这些 他认为每个对象的额外代码?
Haskell相当于将JSON文件反序列化为地图,列表和私有类型的组合,就是读取Value对象。这样就不必定义新记录了。