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

时间:2015-04-19 02:51:44

标签: json haskell aeson

在用于对象序列化/反序列化的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来处理任何类型的对象/ json-string。为什么haskell用户需要为他认为的每个对象编写所有这些额外的代码?
  • 对于具有多个层次结构的复合对象,例如

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

2 个答案:

答案 0 :(得分:6)

  

为什么作者只用一种方法创建ToJSON / FromJSON个实例? toJSON / parseJSON不能单独编写为函数吗?

你误解了很多事情,所以让我清楚一点。 ToJSONFromJSON不是函数。这些是类型。类型类是在Haskell中编写多态代码的一种方法。

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

class ToJSON a where
    toJSON :: a -> String

此语句基本上说:“如果a是类型类ToJSON的实例,那么我们可以使用函数toJSONa序列化为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应用于StringIntDouble的值,并将其分派到正确的实现:< / 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对象。这样就不必定义新记录了。