我正在尝试编写一个将接受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
限制到那个程度,因为那样它对其他情况就不起作用了。我怀疑,如果我在这上面扔出足够的显式类型,我可以使它工作,但感觉它应该更简单。什么是写这个函数的惯用方法,或者如果这是好的那么什么是最简单的方法来获得类型?
答案 0 :(得分:1)
首先,FromJSON a => a
的意思是什么?它的某些内容是什么:它可以是任何类型的东西,但只能来自FromJSON
类。这个类可以包含非常不同构造的类型,你不能进行任何模式匹配。您只能执行程序员在类FromJSON
声明中指定的内容。基本上,有一种方法parseJSON :: FromJSON a => Value -> Parser a
。
其次,您应该为您的工作使用JSON的一些同构表示。类型Value
是好的。因此,您可以通过Value -> Value
之类的功能完成主要工作。之后,您可以使用parseJSON
和toJSON
为常规类型撰写此功能。
像这样:
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
您可以使用lens和lens-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}}"