从 Aeson 中的嵌套 JSON 响应中隔离单个值
Isolate a single value from a nested JSON response in Aeson
我正在处理几个基于 JSON 的 APIs 并且绝大多数时候我只需要从 JSON 响应中提取一个值。例如。使用 {"foo":"xyz","bar":"0.0000012"}
我只需要 bar
.
的值
为此,我编写了函数来提取我需要的信息:
-- | Isolate a Double based on a key from a JSON encoded ByteString
isolateDouble :: String -> B.ByteString -> Maybe Double
isolateDouble k bstr = isolateString k bstr >>= maybeRead
-- | Isolate a String based on a key from a JSON encoded ByteString
isolateString :: String -> B.ByteString -> Maybe String
isolateString k bstr = decode bstr >>= parseMaybe (\obj -> obj .: pack k :: Parser String)
不幸的是,其中一个 API 发送了这样的响应:
{"success":"true","message":"","result":{"foo":"xyz","bar":"0.0000012"}}
显然将其传递给 isolateDouble "bar"
会导致 Nothing
我想我上次这样做时我为响应的两个级别写了 fromJSON
个实例,如下所示:
data Response = Response !Text !Text !Result
instance FromJSON Response where
parseJSON (Object v) = Response <$> v .: "success" <*> v .: "message" <*> v .: "result"
parseJSON _ = mzero
data Result = Result !Text !Text
instance FromJSON Result where
parseJSON (Object v) = Result <$> v .: "foo" <*> v .: "bar"
parseJSON _ = mzero
但随后我将不得不针对数十个不同的 API 调用重复该操作。我知道我也可以使用派生泛型,但不幸的是 JSON 响应中的一些索引有冲突的名称,例如 "id".
考虑到所有这些,将单个值与嵌套 JSON 响应隔离开来的最佳方法是什么?
有无镜头均可。镜头很好,因为它们允许您稍后将您编写的镜头与其他镜头组合在一起。但是,如果你不需要这个,它可能会有点矫枉过正。
首先要意识到,当您编写一个 FromJSON
实例时,您会编写一个具有此签名 parseJSON :: Value -> Parser a
的函数。您无需使用 FromJSON 类型类即可轻松编写这些函数。你真正想要做的是像这样编写 2 个解析器,然后组合它们。
首先,您需要编写一个在对象中查找 'bar' 并将其解析为 Double
:
的程序
parseBar :: Value -> Parser Double
parseBar (Object o) = o .: "bar" >>= maybe (fail "Not a double") return . maybeRead . unpack
parseBar _ = fail "Expected an object."
现在你可以编写另一个函数,使用这个函数来解析一个更嵌套的值:
parseNested :: Value -> Parser Double
parseNested (Object o) = o .: "result" >>= parseBar
parseNested _ = fail "Expected an object."
现在我们编写一个实用函数,在 ByteString
:
上运行解析器
runParser :: (Value -> Parser a) -> BL.ByteString -> Maybe a
runParser p bs = decode bs >>= parseMaybe p
现在我们可以将此函数与我们上面定义的解析器一起使用来解析 json 值,如下所示:
testParseBar = runParser parseBar "{\"foo\":\"xyz\",\"bar\":\"0.0000012\"}"
testParseNested = runParser parseNested "{\"success\":\"true\",\"message\":\"\",\"result\":{\"foo\":\"xyz\",\"bar\":\"0.0000012\"}}"
请注意,您还可以使用 Parsers 上的 Alternative
实例来创建一个解析器来解析以下任一值:
parseBarOrNested :: Value -> Parser Double
parseBarOrNested v = parseBar v <|> parseNested v
此解析器将首先尝试 bar 解析器,如果它不起作用,它将使用嵌套解析器。
testBarOrNestedBar = runParser parseBarOrNested "{\"foo\":\"xyz\",\"bar\":\"0.0000012\"}"
testBarOrNestednested = runParser parseBarOrNested "{\"success\":\"true\",\"message\":\"\",\"result\":{\"foo\":\"xyz\",\"bar\":\"0.0000012\"}}"
这里是包含编译指示和导入的完整代码:
{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson
import Data.Aeson.Types
import Control.Applicative
import qualified Data.ByteString.Lazy as BL
import Data.Text (unpack)
-- Replace with your implementation
maybeRead = Just . read
parseBar :: Value -> Parser Double
parseBar (Object o) = o .: "bar" >>= maybe (fail "Not a double") return . maybeRead . unpack
parseBar _ = fail "Expected an object."
parseNested :: Value -> Parser Double
parseNested (Object o) = o .: "result" >>= parseBar
parseNested _ = fail "Expected an object."
parseBarOrNested :: Value -> Parser Double
parseBarOrNested v = parseBar v <|> parseNested v
runParser :: (Value -> Parser a) -> BL.ByteString -> Maybe a
runParser p bs = decode bs >>= parseMaybe p
testParseBar = runParser parseBar "{\"foo\":\"xyz\",\"bar\":\"0.0000012\"}"
testParseNested = runParser parseNested "{\"success\":\"true\",\"message\":\"\",\"result\":{\"foo\":\"xyz\",\"bar\":\"0.0000012\"}}"
testBarOrNestedBar = runParser parseBarOrNested "{\"foo\":\"xyz\",\"bar\":\"0.0000012\"}"
testBarOrNestednested = runParser parseBarOrNested "{\"success\":\"true\",\"message\":\"\",\"result\":{\"foo\":\"xyz\",\"bar\":\"0.0000012\"}}"
我正在处理几个基于 JSON 的 APIs 并且绝大多数时候我只需要从 JSON 响应中提取一个值。例如。使用 {"foo":"xyz","bar":"0.0000012"}
我只需要 bar
.
为此,我编写了函数来提取我需要的信息:
-- | Isolate a Double based on a key from a JSON encoded ByteString
isolateDouble :: String -> B.ByteString -> Maybe Double
isolateDouble k bstr = isolateString k bstr >>= maybeRead
-- | Isolate a String based on a key from a JSON encoded ByteString
isolateString :: String -> B.ByteString -> Maybe String
isolateString k bstr = decode bstr >>= parseMaybe (\obj -> obj .: pack k :: Parser String)
不幸的是,其中一个 API 发送了这样的响应:
{"success":"true","message":"","result":{"foo":"xyz","bar":"0.0000012"}}
显然将其传递给 isolateDouble "bar"
会导致 Nothing
我想我上次这样做时我为响应的两个级别写了 fromJSON
个实例,如下所示:
data Response = Response !Text !Text !Result
instance FromJSON Response where
parseJSON (Object v) = Response <$> v .: "success" <*> v .: "message" <*> v .: "result"
parseJSON _ = mzero
data Result = Result !Text !Text
instance FromJSON Result where
parseJSON (Object v) = Result <$> v .: "foo" <*> v .: "bar"
parseJSON _ = mzero
但随后我将不得不针对数十个不同的 API 调用重复该操作。我知道我也可以使用派生泛型,但不幸的是 JSON 响应中的一些索引有冲突的名称,例如 "id".
考虑到所有这些,将单个值与嵌套 JSON 响应隔离开来的最佳方法是什么?
有无镜头均可。镜头很好,因为它们允许您稍后将您编写的镜头与其他镜头组合在一起。但是,如果你不需要这个,它可能会有点矫枉过正。
首先要意识到,当您编写一个 FromJSON
实例时,您会编写一个具有此签名 parseJSON :: Value -> Parser a
的函数。您无需使用 FromJSON 类型类即可轻松编写这些函数。你真正想要做的是像这样编写 2 个解析器,然后组合它们。
首先,您需要编写一个在对象中查找 'bar' 并将其解析为 Double
:
parseBar :: Value -> Parser Double
parseBar (Object o) = o .: "bar" >>= maybe (fail "Not a double") return . maybeRead . unpack
parseBar _ = fail "Expected an object."
现在你可以编写另一个函数,使用这个函数来解析一个更嵌套的值:
parseNested :: Value -> Parser Double
parseNested (Object o) = o .: "result" >>= parseBar
parseNested _ = fail "Expected an object."
现在我们编写一个实用函数,在 ByteString
:
runParser :: (Value -> Parser a) -> BL.ByteString -> Maybe a
runParser p bs = decode bs >>= parseMaybe p
现在我们可以将此函数与我们上面定义的解析器一起使用来解析 json 值,如下所示:
testParseBar = runParser parseBar "{\"foo\":\"xyz\",\"bar\":\"0.0000012\"}"
testParseNested = runParser parseNested "{\"success\":\"true\",\"message\":\"\",\"result\":{\"foo\":\"xyz\",\"bar\":\"0.0000012\"}}"
请注意,您还可以使用 Parsers 上的 Alternative
实例来创建一个解析器来解析以下任一值:
parseBarOrNested :: Value -> Parser Double
parseBarOrNested v = parseBar v <|> parseNested v
此解析器将首先尝试 bar 解析器,如果它不起作用,它将使用嵌套解析器。
testBarOrNestedBar = runParser parseBarOrNested "{\"foo\":\"xyz\",\"bar\":\"0.0000012\"}"
testBarOrNestednested = runParser parseBarOrNested "{\"success\":\"true\",\"message\":\"\",\"result\":{\"foo\":\"xyz\",\"bar\":\"0.0000012\"}}"
这里是包含编译指示和导入的完整代码:
{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson
import Data.Aeson.Types
import Control.Applicative
import qualified Data.ByteString.Lazy as BL
import Data.Text (unpack)
-- Replace with your implementation
maybeRead = Just . read
parseBar :: Value -> Parser Double
parseBar (Object o) = o .: "bar" >>= maybe (fail "Not a double") return . maybeRead . unpack
parseBar _ = fail "Expected an object."
parseNested :: Value -> Parser Double
parseNested (Object o) = o .: "result" >>= parseBar
parseNested _ = fail "Expected an object."
parseBarOrNested :: Value -> Parser Double
parseBarOrNested v = parseBar v <|> parseNested v
runParser :: (Value -> Parser a) -> BL.ByteString -> Maybe a
runParser p bs = decode bs >>= parseMaybe p
testParseBar = runParser parseBar "{\"foo\":\"xyz\",\"bar\":\"0.0000012\"}"
testParseNested = runParser parseNested "{\"success\":\"true\",\"message\":\"\",\"result\":{\"foo\":\"xyz\",\"bar\":\"0.0000012\"}}"
testBarOrNestedBar = runParser parseBarOrNested "{\"foo\":\"xyz\",\"bar\":\"0.0000012\"}"
testBarOrNestednested = runParser parseBarOrNested "{\"success\":\"true\",\"message\":\"\",\"result\":{\"foo\":\"xyz\",\"bar\":\"0.0000012\"}}"