【问题标题】:Haskell Yesod - extracting a object inside a json before converting it to a modelHaskell Yesod - 在将其转换为模型之前在 json 中提取对象
【发布时间】:2016-03-08 21:07:08
【问题描述】:

假设我有一个这样的 JSON:

{
  data: {...}
}

{...} 代表我的模型。怎么可能 在这种情况下,在处理程序中获取我的模型?例如,以下内容显然不起作用:

putMyEntityR :: Handler ()
putMyEntityR = do
  (Entity id _) <- (...) -- getting the Key
  e <- requireJsonBody :: Handler MyEntity
  runDB $ replace id e
  sendResponseStatus status204 ("UPDATED" :: Text)

如何读取JSON,获取data 对象,然后才对其进行解码?

【问题讨论】:

  • 我认为显而易见的方法是使用一个简单的包装器并使其成为FromJSON的实例
  • 我不熟悉Yesod,但是你考虑过aeson-lens 吗? Hackage 上的文档让你对你能做什么有一个很好的了解。您不需要提供解析器;您只需查找(或使用任何您想要的镜头魔术)json 字段。

标签: haskell yesod aeson


【解决方案1】:

Github Issue 上有更多关于这个问题的讨论,我在这里添加它作为答案,因为它更加充实。下面是我们使用下面定义的辅助函数得到的 Handler 函数:

postDataCommentR :: Handler Value
postDataCommentR = do
  value <- requireJsonBody' -- Parse request body into Value
  commentJson <- requireJsonKey "data" value -- Lookup a key from the Value
  comment <- (requireJsonParse commentJson :: Handler Comment) -- Parse the Value into a comment record

  insertedComment <- runDB $ insertEntity comment
  returnJson insertedComment

这些函数获取请求正文并将其解析为 aeson Value

import qualified Data.Aeson as J
import qualified Data.Aeson.Parser as JP
import Data.Conduit.Attoparsec (sinkParser)

-- These two functions were written by @FtheBuilder
parseJsonBody' :: (MonadHandler m) => m (J.Result Value)
parseJsonBody' = do
    eValue <- rawRequestBody $$ runCatchC (sinkParser JP.value')
    return $ case eValue of
        Left e -> J.Error $ show e
        Right value -> J.Success value

-- | Same as 'parseJsonBody', but return an invalid args response on a parse
-- error.
requireJsonBody' :: (MonadHandler m) => m Value
requireJsonBody' = do
    ra <- parseJsonBody'
    case ra of
        J.Error s -> invalidArgs [pack s]
        J.Success a -> return a

这些辅助函数用于将 Value 解析为记录:

requireJsonParse :: (MonadHandler m, FromJSON a) => Value -> m a
requireJsonParse v = case J.fromJSON v of
  J.Error s -> invalidArgs [pack s]
  J.Success a -> return a

requireJsonKey :: (MonadHandler m) => Text -> Value -> m Value
requireJsonKey key jObject@(Object hashMap) = case lookup key hashMap of
                                    Nothing -> invalidArgs ["Couldn't find a value when looking up the key " <> key <> " in the object: " <> (pack (show jObject))]
                                    Just v -> return v
requireJsonKey key invalid = invalidArgs ["When looking up the key " <> key <> ", expected an object but got a " ++ (pack (show invalid))]

评论

艾森镜头

我没有使用aeson-lens,但无论有没有它,代码都非常相似,因为我们只是深入一键。如果我们更深入地遍历 JSON,aeson-lens 会让事情变得更好。

与包装器定义的比较

一旦你定义了辅助函数,你还有几行代码来解析Value,然后查找data 键,然后创建你的记录。您可以做一些事情来缩短它,但最终@Carsten 推荐的包装器将具有相似的长度,但复杂性更小,imo。

【讨论】:

  • 你可以把这个函数绑定成一个,这将需要一个like。
  • 这很好,这很干净:(comment :: Comment) &lt;- requireJsonBody' &gt;&gt;= requireJsonKey "data" &gt;&gt;= requireJsonParse
【解决方案2】:

这太大了,不适合我上面的评论(使用lens-aeson

λ: import Control.Lens
λ: import Data.Aeson.Lens
λ: let e = "{ \"data\": [1,2,3] }"
λ: e ^? key "data"
Just (Array [Number 1.0,Number 2.0,Number 3.0])

正如 Carsten 所说,您仍然需要提供模型的 FromJSON 实例

【讨论】:

  • 虽然这是一个不错的方法,但我认为它不会像 requireJsonBody 那样工作 - 这就是为什么我会编写一个简单的 data Wrapper = Wrapper { data :: MyEntity } 并使其成为一个实例 - 实例MyEntity 应该已经存在 - yesod 使用 TH 处理这些事情
  • implementationrequireJsonBody 确实很短。您可以编写自己的函数来解析 aeson Value,如果解析失败则抛出 invalidArgs 异常
  • 这对于@FtheBuilder 的用例来说很可能可以忽略不计,但我认为使用小型包装器会提高内存/CPU 效率,因为 aeson 可以解析 ByteString directly into the record as of aeson-0.10,而使用 aeson-lens您正在使用中间 Value 数据结构。不过,对于大多数用例来说,这可能微不足道。
  • 我很高兴我发布了这个答案,我学到的东西比我能够分享的要多!感谢您的提示。
  • @Carsten - 你的方式很有趣,但它需要编写很多简单的包装器(因为它是一种经常出现的模式)......我认为只提取我的部分需要,然后在我的情况下使用它是一种更好的方法......这就是我喜欢 Nicolas Mattia 评论的原因。
猜你喜欢
  • 2022-01-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-10-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多