在Aeson中隔离来自嵌套JSON响应的单个值 [英] Isolate a single value from a nested JSON response in Aeson

查看:142
本文介绍了在Aeson中隔离来自嵌套JSON响应的单个值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在处理几个基于JSON的API,绝大多数时候我只需要从JSON响应中提取一个值。例如。 {foo:xyz,bar:0.0000012} 我只需要 bar

    -  |根据JSON编码的ByteString中的键隔离Double 
isolateDouble :: String - > B.ByteString - >也许Double
isolateDouble k bstr = isolateString k bstr>> = maybeRead

- |根据JSON编码的ByteString中的键隔离字符串
isolateString :: String - > B.ByteString - > Maybe String
isolateString k bstr = decode bstr>> = parseMaybe(\obj - > obj。:pack k :: Parser String)

$ b

不幸的是,其中一个API会发送如下响应:
{success:true,message: ,result:{foo:xyz,bar:0.0000012}}



isolateDoublebar结果为 Nothing



我认为上次我这样做时,我为这两个级别的响应写了 fromJSON 实例,如下所示:

  data Response = Response!Text!Text!Result 

instance FromJSON Response其中
parseJSON(Object v)= Response< $>诉:成功* v。:消息< *> v。:result
parseJSON _ = mzero

data Result = Result!Text!Text

instance FromJSON Result其中
parseJSON(Object v )=结果< $> v。:foo* v。:bar
parseJSON _ = mzero

但我必须重复对于几十个不同的API调用。我知道我也可以使用派生泛型,但不幸的是JSON响应中的一些索引有冲突的名字,如id。



考虑到所有这些,从嵌套的JSON响应中分离单个值的最佳方法是什么?

解决方案

您可以使用或不使用镜头。镜头很不错,因为它们可以让你组合后来用其他镜头写的镜头。然而,如果你不需要它,它可能是矫枉过正的。



首先认识到,当你写一个 FromJSON 实例你用这个签名来写一个函数 parseJSON :: Value - >解析器a 。您可以轻松编写这些函数,而无需使用FromJSON类型类。你真正想要做的是写2个这样的解析器,然后编写它们。



首先,你需要编写一个在对象中查找'bar'的函数并将它解析为 Double

  parseBar :: Value  - > ;解析器Double 
parseBar(Object o)= o。:bar>> =也许(failNot a double)return。 maybeRead。解压缩
parseBar _ =失败期望一个对象。

现在您可以编写另一个使用此函数解析更多嵌套值的函数:

  parseNested :: Value  - >解析器Double 
parseNested(Object o)= o。:result>> = parseBar
parseNested _ = fail期望一个对象。

现在我们编写一个运行解析器的实用程序函数,它位于 ByteString

  runParser ::(数值 - >解析器a) - > BL.ByteString  - >也许是
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 \}}

请注意,您也可以使用

 <$解析器上的 Alternative  c>实例创建一个解析器来解析这些值中的任何一个: c $ c> parseBarOrNested :: Value  - >解析器Double 
parseBarOrNested v = parseBar v< |> parseNested v

这个解析器将首先尝试解析器,如果它不起作用,它将使用嵌套解析器。

  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

导入合格的Data.ByteString.Lazy作为BL
导入Data.Text(解压缩)

- 用您的实现替换
maybeRead = Just。阅读


parseBar :: Value - >解析器Double
parseBar(Object o)= o。:bar>> =也许(failNot a double)return。 maybeRead。解压缩
parseBar _ =失败期望一个对象。

parseNested :: Value - >解析器Double
parseNested(Object o)= o。:result>> = parseBar
parseNested _ = fail期望一个对象。

parseBarOrNested :: Value - >解析器Double
parseBarOrNested v = parseBar v< |> parseNested v

runParser ::(Value - > Parser a) - > BL.ByteString - >也许是
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\}}


I'm working with a couple of JSON based APIs and the vast majority of the time I only need to extract a single value from the JSON response. E.g. with {"foo":"xyz","bar":"0.0000012"} I only need the value of bar.

To accommodate this I've written functions to extract the information I need:

-- | 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)

Unfortunately, one of the APIs sends the response like this: {"success":"true","message":"","result":{"foo":"xyz","bar":"0.0000012"}}

Obviously passing that to isolateDouble "bar" results in Nothing

I think the last time I did this I wrote fromJSON instances for both levels of the response like so:

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

But then I would have to repeat that for dozens of different API calls. I know I can also use derive generics, but unfortunately some of the indexes in the JSON responses have clashing names like "id".

Given all that, what would be the best way to isolate a single value from a nested JSON response?

解决方案

You can do this with or without lens. Lenses are nice because they allow you to compose the lenses you write with other lenses later. However if you don't need this it might be overkill.

First realize that when you write a FromJSON instance you write a function with this signature parseJSON :: Value -> Parser a. You can easily write these functions without using the FromJSON typeclass. What you actually want to do is write 2 parsers like this, then compose them.

First you need to write the one that will look up a 'bar' in a object and parse it to a Double:

parseBar :: Value -> Parser Double
parseBar (Object o) = o .: "bar" >>= maybe (fail "Not a double") return . maybeRead . unpack
parseBar _          = fail "Expected an object."

Now you can write another function that uses this function to parse a more nested value:

parseNested :: Value -> Parser Double
parseNested (Object o) = o .: "result" >>= parseBar
parseNested _          = fail "Expected an object."

Now we write a utility function that runs the parser on a ByteString:

runParser :: (Value -> Parser a) -> BL.ByteString -> Maybe a 
runParser p bs = decode bs >>= parseMaybe p

Now we can use this function with the parsers we defined above to parse the json values like this:

testParseBar = runParser parseBar "{\"foo\":\"xyz\",\"bar\":\"0.0000012\"}"
testParseNested = runParser parseNested "{\"success\":\"true\",\"message\":\"\",\"result\":{\"foo\":\"xyz\",\"bar\":\"0.0000012\"}}"

Note that you can also use the Alternative instance on Parsers to create one parser that will parse either of these values:

parseBarOrNested :: Value -> Parser Double
parseBarOrNested v = parseBar v <|> parseNested v

This parser will try the bar parser first, if it doesnt work it will use the nested parser.

testBarOrNestedBar = runParser parseBarOrNested "{\"foo\":\"xyz\",\"bar\":\"0.0000012\"}"
testBarOrNestednested = runParser parseBarOrNested "{\"success\":\"true\",\"message\":\"\",\"result\":{\"foo\":\"xyz\",\"bar\":\"0.0000012\"}}"

Here is the complete code with pragmas and imports:

{-# 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\"}}"

这篇关于在Aeson中隔离来自嵌套JSON响应的单个值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆