Add nested property to returned record

霸气de小男生 提交于 2020-01-16 23:12:31

问题


I have a Yesod route handler that returns a JSON with the object

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

I would like to add a _links property with some metadata, that doesn't exist in on the Entity itself, e.g.

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

How can I add the _links to the existing Entity record? Here's my handler:

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

回答1:


To do this you'll have to convert your Entity to a Value manually, then use the functions in unordered-containers:Data.HashMap.Strict to insert the "_links" key, then build a Value out of it again. Using the lens compatible package for aeson could probably simplify this quite a bit though:

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

(I'm assuming that renderedUrl has type Text, change if needed)

Then you can just pass in your Entity and renderedUrl to get a new Value with the "_links" key included. I've used Maybe here to guard against the case where toJSON :: Entity -> Value does not return a Value with the Object constructor. This will protect you in the future if you change the Entity type and how it converts to JSON but forget to update all of your codebase to reflect this change.

EDIT: If you were to use lens-aeson You could write it like this, though this requires swapping the argument order just for neatness:

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

This actually lets you drop the Maybe, you want to anyway, since the way lenses work means that if the Object isn't the top level then the original value is returned, so buildEntityWithLink "testlink" ([] :: [Entity]) Would just return the same as toJSON ([] :: [Entity]), namely an empty Array. The toJSON has to be on the outside of the lens operation because in order to compose with _Object it has to be able to be a setter, and toJSON can't be easily made into a setter. Instead, we just pre-process our Entity into a Value, then feed it in to the lens expression. I've added whitespace where I've lined up the arguments to each function, makes it a bit more readable IMO, but this is all technically 1 line of code. A useful feature about this implementation is that it's now easy to relax the type signature to ToJSON a => Text -> a -> Value, so you can add a _links to any type you want to.




回答2:


Based on bheklilr's working comment - here's the final code I now have:

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

And now indeed, the JSON appears as expected with the self link



来源:https://stackoverflow.com/questions/34907953/add-nested-property-to-returned-record

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!