memo-pad’s blog

自分のためのメモをまとめています。ここに書いてある内容については責任は負いません。全て自己責任でお願いします。

SpringBootでPartial Response

状況

  • API作るときに、fields=xxx,yyy,zzzというように欲しいフィールドだけ指定できるようにしたい

実現方法

  • 返り値をMappingJacksonValueに変更し、Filterを設定することで実現
  • コードはこちら github.com

具体的な実装

データクラス

  • Filter対象のクラスに@JsonFilter()で名前をつける
@Data
@AllArgsConstructor
@JsonFilter("User")
public class User {
    private String id;
    private String name;
    private UserInfo userInfo;
}
@Data
@AllArgsConstructor
public class UserInfo {
    private String tel;
    private String address;
}

コントローラ

  • 返り値はUserではなく、MappingJacksonValueで返す
    • SimpleFilterProviderのaddFilterでFilterを作成
    • 第一引数には@JsonFilterで指定した名前を、第二引数にはフィルタープロパティを設定
    • 今回はfilterOutAllExcept(指定した物以外をすべてフィルタ)を利用
@RestController
public class ResponseFilterController {

    private final Map<String, User> users = new HashMap<String, User>() {
        {
            put("1", new User("1", "user1", new UserInfo("xxx-xxxx-1111", "XXXXXXXXXXXXX1")));
            put("2", new User("2", "user2", new UserInfo("xxx-xxxx-2222", "XXXXXXXXXXXXX2")));
            put("3", new User("3", "user3", new UserInfo("xxx-xxxx-3333", "XXXXXXXXXXXXX3")));

        }
    };

    @GetMapping("/user/{user_id}")
    MappingJacksonValue getSingleUser(
            @PathVariable("user_id") String userId,
            @RequestParam(value = "fields", defaultValue = "name", required = false) String fieldParam
    ) {
        User user = users.get(userId);

        return createMappingJacksonValue(fieldParam, user);
    }

    private MappingJacksonValue createMappingJacksonValue(String fieldParam, Object value) {
        String[] fields = fieldParam.split(",", 0);

        MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(value);
        FilterProvider filters = new SimpleFilterProvider()
                // 第一引数には@JsonFilterで指定した名前、第二引数にはフィルタープロパティ
                // filterOutAllExcept(指定した物以外をすべてフィルタ)
                .addFilter("User", SimpleBeanPropertyFilter.filterOutAllExcept(fields));
        mappingJacksonValue.setFilters(filters);
        return mappingJacksonValue;
    }
}

application.propeties

  • レスポンスのfieldをSnakeCaseに(あってもなくても可)
spring.jackson.property-naming-strategy=SNAKE_CASE

実行結果

  • fields=id,nameと指定 f:id:memo-pad:20181008164612p:plain

  • fields=id,name,user_infoと指定
    こんな感じでuser_info配下を一括で表示制御もできる f:id:memo-pad:20181008164633p:plain

Filter対象のクラスを含むクラスの場合

こんな感じに件数と複数のUserを持つクラスの場合も、User側にフィルタ設定があるので特にフィルタを追加することなく実現可能

データクラス

@Data
@AllArgsConstructor
public class UserResponse {
    private Integer totalResults;
    private Collection<User> results;
}

コントローラ

 @GetMapping("/user")
    MappingJacksonValue getUser(
            @RequestParam(value = "fields", defaultValue = "name", required = false) String fieldParam
    ) {
        UserResponse userResponse = new UserResponse(users.size(), users.values());

        return createMappingJacksonValue(fieldParam, userResponse);
    }

実行結果

  • fields=id,nameと指定
    こんな感じにJsonFilter指定したクラスを要素に持つ場合もちゃんとフィルタされる f:id:memo-pad:20181008164648p:plain