用 aeson 转换 JSON 值字段
Convert JSON value field with aeson
场景:我需要读入一个 JSON 文件,然后将 abcs 中的值字段更新为绝对路径。
与值字段相关的键不是静态的,因此我想使用散列映射来执行它。
我的挑战是,我一直在类型中转来转去,无法弄清楚如何转换它们。理想情况下,updatePaths 应该 return IO Object.
JSON:
{
"abcs": {
"{crtl}": "crtl.abc",
"{wt}": "wt.abc"
}
}
Haskell:
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
module TCT.ScenarioRunner where
import Network.HTTP.Simple
import Data.Aeson
import GHC.Generics
import qualified Data.ByteString.Lazy as B
import qualified Data.HashMap.Strict as Hm
import Data.Text.Internal
import Filesystem.Path
import System.Path.NameManip (absolute_path)
loadFile :: IO B.ByteString
loadFile = B.readFile "resources/initialise_body.json"
initialise :: String -> IO ()
initialise sessionId = do
raw <- loadFile
let json = (eitherDecode raw) :: Either String Value
case json of
Left err -> putStrLn err
Right (Object ps) ->
case Hm.lookup "abcs" ps of
Nothing -> putStrLn "Could not find abcs"
Just (Object abcs) -> do
(putStrLn "Found abcs")
result <- print $ updatePaths abcs
putStrLn "Bla"
putStrLn "TBD: initialise"
updatePaths :: Object -> Object
updatePaths obj = Hm.map createFullPath obj
where createFullPath val = absolute_path ("resources/abcs/" ++ val)
错误:
src/TCT/ScenarioRunner.hs:71:22-46: error: …
• Couldn't match type ‘IO String’ with ‘Value’
Expected type: Object
Actual type: Hm.HashMap Text (IO String)
• In the expression: Hm.map createFullPath obj
In an equation for ‘updatePaths’:
updatePaths obj
= Hm.map createFullPath obj
where
createFullPath val = absolute_path ("resources/abcs/" ++ val)
|
src/TCT/ScenarioRunner.hs:71:44-46: error: …
• Couldn't match type ‘Value’ with ‘[Char]’
Expected type: Hm.HashMap Text [Char]
Actual type: Object
• In the second argument of ‘Hm.map’, namely ‘obj’
In the expression: Hm.map createFullPath obj
In an equation for ‘updatePaths’:
updatePaths obj
= Hm.map createFullPath obj
where
createFullPath val = absolute_path ("resources/abcs/" ++ val)
|
Compilation failed.
更新:
Probies 解决方案几乎是正确的,我做了一些修改,所以解决方案变成了:
updatePaths :: Object -> IO Object
updatePaths (obj :: Object) = traverse createFullPath obj
where
createFullPath :: Value -> IO Value
createFullPath (String val) =
(String . T.pack) <$> absolute_path ("resources/abcs/" ++ (T.unpack val))
createFullPath x = pure x -- Ignore non strings
更新二:
为了更好地理解解决方案,我查看了遍历类型签名。
遍历具有类型签名:... => (a -> f b) -> t a -> f (t b)
a
必须是 Value
;
f
必须是 IO
;
t
必须是 HashMap Text
,因为 Object
是 HashMap Text Value
的类型同义词
这为我们提供了:(Value -> IO Value) -> HashMap Text Value -> IO (HashMap Text Value)
或所需的 IO 对象。
有几个问题。首先,你有一个 Aeson Value
的 HashMap,而不是 String
,所以我们需要进行模式匹配才能得到我们的 "String"。其次,"String type" Aeson 使用的是 Text
,而 absolute_path
想要一个常规字符串,因此我们需要进行一些转换。第三,absolute_path
将 return 设为 IO
值,因此我们需要使用 traverse
而不是 map
。
所以,假设您已经将 Data.Text
导入为 T
updatePaths :: Object -> IO Object
updatePaths obj = traverse createFullPath obj
where
createFullPath (String val) =
(String . T.pack) <$> absolute_path ("resources/abcs/" ++ (T.unpack val))
createFullPath x = pure x -- Ignore non strings
场景:我需要读入一个 JSON 文件,然后将 abcs 中的值字段更新为绝对路径。
与值字段相关的键不是静态的,因此我想使用散列映射来执行它。
我的挑战是,我一直在类型中转来转去,无法弄清楚如何转换它们。理想情况下,updatePaths 应该 return IO Object.
JSON:
{
"abcs": {
"{crtl}": "crtl.abc",
"{wt}": "wt.abc"
}
}
Haskell:
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
module TCT.ScenarioRunner where
import Network.HTTP.Simple
import Data.Aeson
import GHC.Generics
import qualified Data.ByteString.Lazy as B
import qualified Data.HashMap.Strict as Hm
import Data.Text.Internal
import Filesystem.Path
import System.Path.NameManip (absolute_path)
loadFile :: IO B.ByteString
loadFile = B.readFile "resources/initialise_body.json"
initialise :: String -> IO ()
initialise sessionId = do
raw <- loadFile
let json = (eitherDecode raw) :: Either String Value
case json of
Left err -> putStrLn err
Right (Object ps) ->
case Hm.lookup "abcs" ps of
Nothing -> putStrLn "Could not find abcs"
Just (Object abcs) -> do
(putStrLn "Found abcs")
result <- print $ updatePaths abcs
putStrLn "Bla"
putStrLn "TBD: initialise"
updatePaths :: Object -> Object
updatePaths obj = Hm.map createFullPath obj
where createFullPath val = absolute_path ("resources/abcs/" ++ val)
错误:
src/TCT/ScenarioRunner.hs:71:22-46: error: …
• Couldn't match type ‘IO String’ with ‘Value’
Expected type: Object
Actual type: Hm.HashMap Text (IO String)
• In the expression: Hm.map createFullPath obj
In an equation for ‘updatePaths’:
updatePaths obj
= Hm.map createFullPath obj
where
createFullPath val = absolute_path ("resources/abcs/" ++ val)
|
src/TCT/ScenarioRunner.hs:71:44-46: error: …
• Couldn't match type ‘Value’ with ‘[Char]’
Expected type: Hm.HashMap Text [Char]
Actual type: Object
• In the second argument of ‘Hm.map’, namely ‘obj’
In the expression: Hm.map createFullPath obj
In an equation for ‘updatePaths’:
updatePaths obj
= Hm.map createFullPath obj
where
createFullPath val = absolute_path ("resources/abcs/" ++ val)
|
Compilation failed.
更新: Probies 解决方案几乎是正确的,我做了一些修改,所以解决方案变成了:
updatePaths :: Object -> IO Object
updatePaths (obj :: Object) = traverse createFullPath obj
where
createFullPath :: Value -> IO Value
createFullPath (String val) =
(String . T.pack) <$> absolute_path ("resources/abcs/" ++ (T.unpack val))
createFullPath x = pure x -- Ignore non strings
更新二:
为了更好地理解解决方案,我查看了遍历类型签名。
遍历具有类型签名:... => (a -> f b) -> t a -> f (t b)
a
必须是 Value
;
f
必须是 IO
;
t
必须是 HashMap Text
,因为 Object
是 HashMap Text Value
这为我们提供了:(Value -> IO Value) -> HashMap Text Value -> IO (HashMap Text Value)
或所需的 IO 对象。
有几个问题。首先,你有一个 Aeson Value
的 HashMap,而不是 String
,所以我们需要进行模式匹配才能得到我们的 "String"。其次,"String type" Aeson 使用的是 Text
,而 absolute_path
想要一个常规字符串,因此我们需要进行一些转换。第三,absolute_path
将 return 设为 IO
值,因此我们需要使用 traverse
而不是 map
。
所以,假设您已经将 Data.Text
导入为 T
updatePaths :: Object -> IO Object
updatePaths obj = traverse createFullPath obj
where
createFullPath (String val) =
(String . T.pack) <$> absolute_path ("resources/abcs/" ++ (T.unpack val))
createFullPath x = pure x -- Ignore non strings