问题 弹簧静止控制器返回特定字段


我一直在思考使用Spring MVC设计JSON API的最佳方法。我们都知道IO很昂贵,因此我不想让客户端进行多次API调用以获得他们需要的东西。但与此同时,我不一定要回厨房水槽。

作为一个例子,我正在开发类似于IMDB的游戏API,而不是用于视频游戏。

如果我返回与游戏相关的所有内容,它将看起来像这样。

/ API /游戏/ 1

{
    "id": 1,
    "title": "Call of Duty Advanced Warfare",
    "release_date": "2014-11-24",
    "publishers": [
        {
            "id": 1,
            "name": "Activision"
        }
    ],
    "developers": [
        {
            "id": 1,
            "name": "Sledge Hammer"
        }
    ],
    "platforms": [
        {
            "id": 1,
            "name": "Xbox One",
            "manufactorer": "Microsoft",
            "release_date": "2013-11-11"
        },
        {
            "id": 2,
            "name": "Playstation 4",
            "manufactorer": "Sony",
            "release_date": "2013-11-18"
        },
        {
            "id": 3,
            "name": "Xbox 360",
            "manufactorer": "Microsoft",
            "release_date": "2005-11-12"
        }
    ],
    "esrbRating": {
        "id": 1,
        "code": "T",
        "name": "Teen",
        "description": "Content is generally suitable for ages 13 and up. May contain violence, suggestive themes, crude humor, minimal blood, simulated gambling and/or infrequent use of strong language."
    },
    "reviews": [
        {
            "id": 1,
            "user_id": 111,
            "rating": 4.5,
            "description": "This game is awesome"
        }
    ]
}

然而,他们可能不需要所有这些信息,但他们可能会再次。从I / O和性能来看,调用所有内容似乎是一个坏主意。

我想通过在请求中指定include参数来做到这一点。

现在,例如,如果您没有指定任何包含,那么您将获得以下内容。

{
    "id": 1,
    "title": "Call of Duty Advanced Warfare",
    "release_date": "2014-11-24"
}

但是,您希望所有信息的请求看起来像这样。

/api/game/1?include=publishers,developers,platforms,reviews,esrbRating

这样,客户端就能够指定他们想要的信息量。但是,使用Spring MVC实现这一点时,我有点不知所措。

我在想控制器看起来像这样。

public @ResponseBody Game getGame(@PathVariable("id") long id, 
    @RequestParam(value = "include", required = false) String include)) {

        // check which include params are present

        // then someone do the filtering?
}

我不确定你如何选择序列化Game对象。这是否可能。在Spring MVC中处理此问题的最佳方法是什么?

仅供参考,我正在使用Spring Boot,其中包括Jackson进行序列化。


4687
2018-05-31 14:49


起源

好像你在这里做了一些过早的优化。您的实体中是否有这么多数据需要根据客户端的请求进行过滤?根据您显示的内容,您将使客户端和服务器过于复杂并破坏服务的RESTfullness,同时不会节省太多IO。 - Sergei Petunin
在我的例子中,我同意这绝对是矫枉过正。让我们举个例子说,虽然返回Game对象会产生一个巨大的JSON对象,你是说它会更好做/ game / 1 / reviews,而不是/ game / 1?include = reviews? - greyfox
如果对象很大,那么我会从单独的子程序请求集合,因为发送多个请求的开销无论如何都与传输数据的总量有关。 - Sergei Petunin
stackoverflow.com/questions/23101260/... - Hett


答案:


而不是返回 Game 对象,您可以将其序列化为a Map<String, Object>,其中映射键表示属性名称。因此,您可以根据需要将值添加到地图中 include 参数。

@ResponseBody
public Map<String, Object> getGame(@PathVariable("id") long id, String include) {

    Game game = service.loadGame(id);
    // check the `include` parameter and create a map containing only the required attributes
    Map<String, Object> gameMap = service.convertGameToMap(game, include);

    return gameMap;

}

举个例子,如果你有一个 Map<String, Object> 喜欢这个:

gameMap.put("id", game.getId());
gameMap.put("title", game.getTitle());
gameMap.put("publishers", game.getPublishers());

它会像这样序列化:

{
  "id": 1,
  "title": "Call of Duty Advanced Warfare",
  "publishers": [
    {
        "id": 1,
        "name": "Activision"
    }
  ]
}

10
2018-05-31 15:20



哇,这实际上是一种简单而灵活的处理方式。 - greyfox
使用自定义服务执行此操作对我来说听起来不是一个好主意,有没有“Spring”方式这样做? - EralpB
@EralpB你还可以搜索如何根据他们的名字(使用杰克逊)序列化字段或使用像GraphQL这样的东西。除此之外,我不确定是否有“弹簧”方式来实现相同的结果。 - Marlon Bernardes
我们如何将Game类转换为Map? - Kabindra Shrestha