【问题标题】:Spring Rest Controller Return Specific FieldsSpring Rest 控制器返回特定字段
【发布时间】:2015-08-14 01:03:26
【问题描述】:

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

例如,我正在开发一个类似于 IMDB 但用于视频游戏的游戏 API。

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

/api/game/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 和性能来看,对所有内容进行调用似乎是个坏主意。

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

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

{
    "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 中解决此问题的最佳方法是什么?

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

【问题讨论】:

  • 好像你在这里做了一些过早的优化。您的实体中是否真的有如此多的数据,您需要根据客户的请求对其进行过滤?根据您所展示的内容,您将过度复杂化客户端和服务器并破坏服务的 RESTfullness,同时不会节省太多 IO。
  • 在我的例子中,我同意这绝对是矫枉过正。让我们举个例子,虽然返回 Game 对象会导致一个巨大的 JSON 对象,你是说做 /game/1/reviews 会更好,而不是 /game/1?include=reviews?
  • 如果对象很大,那么我会从单独的子资源请求集合,因为相对于传输的数据总量而言,发出多个请求的开销无论如何都会很小。

标签: java spring rest spring-mvc spring-restcontroller


【解决方案1】:

解决方案 1: 将 @JsonIgnore 添加到您不想包含在 API 响应中的变量(在模型中)

@JsonIgnore
    private Set<Student> students;

解决方案 2: 删除您不想包含的变量的 getter。

如果您在其他地方需要它们,请为 getter 使用不同的格式,以便 spring 不知道它。

【讨论】:

    【解决方案2】:

    这可以通过 Spring Projections 完成。也适用于 Kotlin。 看这里:https://www.baeldung.com/spring-data-jpa-projections

    【讨论】:

      【解决方案3】:

      意识到我的回答来得太晚了:我建议您查看Projections

      您所要求的是预测的内容。

      既然你问的是 Spring,我想试试这个: https://docs.spring.io/spring-data/rest/docs/current/reference/html/#projections-excerpts

      【讨论】:

        【解决方案4】:

        您可以将其序列化为Map&lt;String, Object&gt;,而不是返回Game 对象,其中映射键表示属性名称。因此,您可以根据 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&lt;String, Object&gt;

        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"
            }
          ]
        }
        

        【讨论】:

        • 使用自定义服务来做这件事对我来说听起来不是一个好主意,有没有“春天”的方式来做这件事?
        • @EralpB 您还可以搜索如何根据字段名称(使用 Jackson)序列化字段或使用 GraphQL 之类的东西。除此之外,我不确定是否有一种“弹簧”方式可以达到同样的效果。
        • 我们如何将 Game 类转换为 Map?
        • 是否可以提供一个示例或扩展答案与如何实现 'service.convertGameToMap(game, include)' 方法?包含它的最佳做法是什么,等等。
        猜你喜欢
        • 2017-02-19
        • 2019-06-06
        • 1970-01-01
        • 2023-03-27
        • 2019-05-07
        • 2013-08-22
        • 2015-08-04
        • 2014-06-28
        • 1970-01-01
        相关资源
        最近更新 更多