在Haskell中递归更改JSON数据结构

时间:2016-12-23 14:54:50

标签: json haskell

我正在尝试编写一个将接受JSON对象的函数,对其中的每个字符串值进行更改并返回一个新的JSON对象。到目前为止,我的代码是:

applyContext :: FromJSON a => a -> a
applyContext x =
  case x of
    Array _  -> map applyContext x
    Object _ -> map applyContext x
    String _ -> parseValue x
    _        -> x

然而,编译器抱怨第二秒case行:

Couldn't match expected type `[b0]' with actual type `a'
  `a' is a rigid type variable bound by
    the type signature for:
      applyContext :: forall a. FromJSON a => a -> a
    at app\Main.hs:43:17

我猜这是因为map意味着在列表上工作,但我会天真地期望它使用Data.HashMap.Lazy.map代替,因为在这种情况下,这就是类型实际上的类型。如果我明确使用该功能,我会得到

Couldn't match expected type `HashMap.HashMap k0 v20' with actual type `a'

这也是有道理的,因为我没有将a限制到那个程度,因为那样它对其他情况就不起作用了。我怀疑,如果我在这上面扔出足够的显式类型,我可以使它工作,但感觉它应该更简单。什么是写这个函数的惯用方法,或者如果这是好的那么什么是最简单的方法来获得类型?

1 个答案:

答案 0 :(得分:1)

首先,FromJSON a => a的意思是什么?它的某些内容是什么:它可以是任何类型的东西,但只能来自FromJSON类。这个类可以包含非常不同构造的类型,你不能进行任何模式匹配。您只能执行程序员在类FromJSON声明中指定的内容。基本上,有一种方法parseJSON :: FromJSON a => Value -> Parser a

其次,您应该为您的工作使用JSON的一些同构表示。类型Value是好的。因此,您可以通过Value -> Value之类的功能完成主要工作。之后,您可以使用parseJSONtoJSON为常规类型撰写此功能。

像这样:

change :: Value -> Value
change (Array x)  = Array . fmap change $ x
change (Object x) = Object . fmap change $ x
change (String x) = Object . parseValue $ x
change x          = x

apply :: (ToJSON a, FromJSON b) => (Value -> Value) -> a -> Result b
apply change = fromJSON . change . toJSON

unsafeApply :: (ToJSON a, FromJSON b) => (Value -> Value) -> a -> b
unsafeApply change x = case apply change x of
                         Success x -> x
                         Error msg -> error $ "unsafeApply: " ++ msg

applyContext :: (ToJSON a, FromJSON b) => a -> b
applyContext = unsafeApply change

您可以使用lenslens-aeson编写更复杂的转化,例如Value -> Value。例如:

import Control.Lens
import Control.Monad.State
import Data.Aeson
import Data.Aeson.Lens
import Data.Text.Lens
import Data.Char

change :: Value -> Value
change = execState go
  where
    go = do
        zoom values go
        zoom members go
        _String . _Text . each %= toUpper
        _Bool %= not
        _Number *= 10

main = print $ json & _Value %~ change
  where json = "{\"a\":[1,\"foo\",false],\"b\":\"bar\",\"c\":{\"d\":5}}"

输出将是:

"{\"a\":[10,\"FOO\",true],\"b\":\"BAR\",\"c\":{\"d\":50}}"