优化基于镜头的JSON处理

时间:2019-01-29 20:31:12

标签: json haskell aeson lenses

在我当前的“ learning haskell”项目中,我尝试从第三方api获取天气数据。我想从以下响应正文中提取namemain.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)

我坚持在maybeCityNamemaybeTemp中优化JSON解析部分,我的想法是:

  1. 当前,JSON被解析两次(我对原始响应^?两次应用responseBody r)。
  2. 我想一次获得数据。 ?..可以获取值列表。但是我提取了不同的类型(StringDouble),所以?..在这里不合适。

我正在寻找更优雅/更自然的方法来安全地解析JSON,读取所需的值并将其应用于数据构造函数GetCityWeather。在此先感谢您的帮助和反馈。

更新:使用折叠功能,我可以通过两个大小写匹配来解决问题

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)

1 个答案:

答案 0 :(得分:1)

@jpath指出,这里真正的问题是关于lens和JSON处理的问题。问题的症结似乎是您想一次完成镜头操作。为此,请查看方便的ReifiedFold:您想要的“并行”功能已打包到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的{​​{1}} / ToJSON