优化基于镜头的 JSON 处理
Optimize lens based JSON handling
在我当前的 "learning haskell" 项目中,我尝试从第三方 api 获取天气数据。我想从以下响应正文中提取 name
和 main.temp
值:
{
...
"main": {
"temp": 280.32,
...
},
...
"name": "London",
...
}
我编写了一个 getWeather
服务来执行 IO 并转换响应以构建 GetCityWeather
数据:
....
data WeatherService = GetCityWeather String Double
deriving (Show)
....
getWeather :: IO (ServiceResult WeatherService)
getWeather = do
...
response <- httpLbs request manager
...
-- work thru the response
return $ case ((maybeCityName response, maybeTemp response)) of
(Just name, Just temp) -> success name temp
bork -> err ("borked data >:( " ++ show bork))
where
showStatus r = show $ statusCode $ responseStatus r
maybeCityName r = (responseBody r)^?key "name"._String
maybeTemp r = (responseBody r)^?key "main".key "temp"._Double
success n t = Right (GetCityWeather (T.unpack n) t)
err e = Left (SimpleServiceError e)
我在maybeCityName
和maybeTemp
中坚持优化JSON解析部分,我的想法是:
- 目前 JSON 被解析了两次(我在原始响应
responseBody r
上应用了两次 ^?
)。
- 我想获取"one shot"中的数据。
?..
能够获取值列表。但是我提取了不同的类型(String
、Double
),所以 ?..
不适合这里。
我正在寻找更优雅/更自然的方法来安全地解析 JSON,读取所需的值并将它们应用于数据构造函数 GetCityWeather
。在此先感谢您的帮助和反馈。
更新:使用 Folds 我可以通过两个大小写匹配来解决问题
getWeather :: IO (ServiceResult WeatherService)
getWeather = do
...
let value = decode $ responseBody response
return $ case value of
Just v -> case (v ^? weatherService) of
Just wr -> Right wr
Nothing -> err "incompatible data"
Nothing -> err "bad json"
where
err t = Left (SimpleServiceError t)
weatherService :: Fold Value WeatherService
weatherService = runFold $ GetCityWeather
<$> Fold (key "name" . _String . unpacked)
<*> Fold (key "main" . key "temp" . _Double)
正如@jpath 指出的那样,您在这里遇到的真正问题是关于 lens
和 JSON 处理的问题。问题的症结似乎是您想一次完成镜头操作。为此,请查看方便的 ReifiedFold
:您想要的 "parallel" 功能已打包到 Applicative
实例中。
import Control.Lens
import Data.Aeson
import Data.Aeson.Lens
import Data.Text.Lens ( unpacked )
-- | Extract a `WeatherService` from a `Value` if possible
weatherService :: Fold Value WeatherService
weatherService = runFold $ GetCityWeather
<$> Fold (key "name" . _String . unpacked)
<*> Fold (key "main" . key "temp" . _Double))
然后,您可以尝试一次性获取您的 WeatherService
:
...
-- work thru the response
let body = responseBody r
return $ case body ^? weatherService of
Just wr -> Right wr
Nothing -> Left (SimpleServiceError ("borked data >:( " ++ show body))
但是,为了错误消息的缘故,如果您计划进一步扩展它,那么利用 aeson
的 ToJSON
/FromJSON
可能是更好的主意。
在我当前的 "learning haskell" 项目中,我尝试从第三方 api 获取天气数据。我想从以下响应正文中提取 name
和 main.temp
值:
{
...
"main": {
"temp": 280.32,
...
},
...
"name": "London",
...
}
我编写了一个 getWeather
服务来执行 IO 并转换响应以构建 GetCityWeather
数据:
....
data WeatherService = GetCityWeather String Double
deriving (Show)
....
getWeather :: IO (ServiceResult WeatherService)
getWeather = do
...
response <- httpLbs request manager
...
-- work thru the response
return $ case ((maybeCityName response, maybeTemp response)) of
(Just name, Just temp) -> success name temp
bork -> err ("borked data >:( " ++ show bork))
where
showStatus r = show $ statusCode $ responseStatus r
maybeCityName r = (responseBody r)^?key "name"._String
maybeTemp r = (responseBody r)^?key "main".key "temp"._Double
success n t = Right (GetCityWeather (T.unpack n) t)
err e = Left (SimpleServiceError e)
我在maybeCityName
和maybeTemp
中坚持优化JSON解析部分,我的想法是:
- 目前 JSON 被解析了两次(我在原始响应
responseBody r
上应用了两次^?
)。 - 我想获取"one shot"中的数据。
?..
能够获取值列表。但是我提取了不同的类型(String
、Double
),所以?..
不适合这里。
我正在寻找更优雅/更自然的方法来安全地解析 JSON,读取所需的值并将它们应用于数据构造函数 GetCityWeather
。在此先感谢您的帮助和反馈。
更新:使用 Folds 我可以通过两个大小写匹配来解决问题
getWeather :: IO (ServiceResult WeatherService)
getWeather = do
...
let value = decode $ responseBody response
return $ case value of
Just v -> case (v ^? weatherService) of
Just wr -> Right wr
Nothing -> err "incompatible data"
Nothing -> err "bad json"
where
err t = Left (SimpleServiceError t)
weatherService :: Fold Value WeatherService
weatherService = runFold $ GetCityWeather
<$> Fold (key "name" . _String . unpacked)
<*> Fold (key "main" . key "temp" . _Double)
正如@jpath 指出的那样,您在这里遇到的真正问题是关于 lens
和 JSON 处理的问题。问题的症结似乎是您想一次完成镜头操作。为此,请查看方便的 ReifiedFold
:您想要的 "parallel" 功能已打包到 Applicative
实例中。
import Control.Lens
import Data.Aeson
import Data.Aeson.Lens
import Data.Text.Lens ( unpacked )
-- | Extract a `WeatherService` from a `Value` if possible
weatherService :: Fold Value WeatherService
weatherService = runFold $ GetCityWeather
<$> Fold (key "name" . _String . unpacked)
<*> Fold (key "main" . key "temp" . _Double))
然后,您可以尝试一次性获取您的 WeatherService
:
...
-- work thru the response
let body = responseBody r
return $ case body ^? weatherService of
Just wr -> Right wr
Nothing -> Left (SimpleServiceError ("borked data >:( " ++ show body))
但是,为了错误消息的缘故,如果您计划进一步扩展它,那么利用 aeson
的 ToJSON
/FromJSON
可能是更好的主意。