添加嵌套 属性 到返回的记录

Add nested property to returned record

我有一个 Yesod 路由处理程序,它 returns 一个 JSON 对象

{ id: 1,
  title: "foo"
  content: "bar"
}

我想添加一个 _links 属性 和一些实体本身不存在的元数据,例如

{ id: 1,
  title: "foo"
  content: "bar"
  _links: {self: http://localhost:3000/events/1}
}

如何将 _links 添加到现有的实体记录中?这是我的处理程序:

getEventR :: EventId -> Handler Value
getEventR eid = do
    event <- runDB $ get404 eid

    render <- getUrlRender
    let renderedUrl = render $ EventR eid

    let links = object
          [ "self" .= renderedUrl
          ]

    let returnVal = object
          [ "data" .= (Entity eid event)
          , "_links" .= links
          ]

    return returnVal

为此,您必须手动将 Entity 转换为 Value,然后使用 unordered-containers:Data.HashMap.Strict 中的函数插入 "_links" 键,然后再次从中构建一个 Value。不过,为 aeson 使用镜头兼容包可能会大大简化这一过程:

buildEntityWithLink :: Entity -> Text -> Maybe Value
buildEntityWithLink entity renderedUrl = case toJSON entity of
    Object obj -> 
        let links = object ["self" .= renderedUrl]
            entityWithLink = HashMap.insert "_links" links obj
        in Just (Object entityWithLink)
    _ -> Nothing

(我假设 renderedUrl 的类型为 Text,如果需要可以更改)

然后您只需传入 EntityrenderedUrl 即可获得包含 "_links" 密钥的新 Value。我在这里使用 Maybe 来防止 toJSON :: Entity -> Value 不使用 Object 构造函数 return 和 Value 的情况。如果您更改 Entity 类型及其转换为 JSON 的方式,但忘记更新所有代码库以反映此更改,这将在将来保护您。

编辑:如果你要使用 lens-aeson 你可以这样写,尽管这需要为了整洁而交换参数顺序:

buildEntityWithLink :: Text -> Entity -> Value
buildEntityWithLink renderedUrl = (
    over _Object $
         HashMap.insert "_links" $
                        object ["self" .= renderedUrl]
    ) . toJSON

这实际上可以让您删除 Maybe,无论如何,因为镜头的工作方式意味着如果 Object 不是最高级别,那么原始值是 returned,所以 buildEntityWithLink "testlink" ([] :: [Entity]) 将 return 与 toJSON ([] :: [Entity]) 相同,即空 ArraytoJSON 必须在镜头操作的外部,因为为了与 _Object 组合,它必须能够成为 setter,而 toJSON 不能很容易做成setter。相反,我们只是 pre-process 我们的 Entity 变成 Value,然后将其输入到镜头表达式中。我在排列每个函数的参数的地方添加了空格,使它在我看来更具可读性,但这在技术上都是一行代码。此实现的一个有用特性是,现在可以轻松地将类型签名放宽为 ToJSON a => Text -> a -> Value,因此您可以将 _links 添加到您想要的任何类型。

基于 working 评论 - 这是我现在的最终代码:

import Data.HashMap.Strict as HashMap (insert)

getEventR :: EventId -> Handler Value
getEventR eid = do
    event <- runDB $ get404 eid

    render <- getUrlRender
    let renderedUrl = render $ EventR eid

    let returnVal = object
          [ "data" .= [buildEntityWithLink (Entity eid event) renderedUrl]]

    return returnVal


buildEntityWithLink :: Entity Event -> Text -> Maybe Value
buildEntityWithLink entity renderedUrl =
    case toJSON entity of
        Object obj ->
            let links = object ["self" .= renderedUrl]
                entityWithLink = HashMap.insert "_links" links obj
            in Just (Object entityWithLink)
        _ -> Nothing

现在确实,JSON 与 self link

一起出现