Works by

Ren's blog

@rennnosuke_rk 技術ブログです

【Spring/Jackson】Rest API : JSON RequestBodyのプロパティをEnum型としてマッピングする

f:id:rennnosukesann:20181220183020p:plain

メモ。

SpringのControllerでは、 @RequestBody を使用することでHTTPリクエストボディに設定されたパラメータをJSONで受け取ることができます。

@RestController
public class FluitController {
    // Request Body にJSONを指定するAPI
    @RequestMapping(value = "/fluits", method = RequestMethod.POST)
    public FluitResponse post(@RequestBody FluitRequest req) {
        ....
    }
}

このとき、 HogeRequest が以下のような構造で定義されているとします。

@Data
public class FluitRequest {
    private String name;
    private Integer price;
    private Integer category;
}

FluitRequest::category は以下の値域のみとり得るとします

HogeRequest req = new HogeRequest();
req.setCategory(1); // 1 : りんご
req.setCategory(2); // 2 : みかん
req.setCategory(3); // 3 : ぶどう

FluitRequest::category のような有限集合の場合、Javaであれば列挙型を使用して定義したいところです。

public enum FluitCategory {
    APPLE, ORANGE, GRAPE
}

FluitRequestAPI /fluits のPOSTリクエストボディがマッピングされるとき、category プロパティの値がそのまま FluitCategory 列挙型としてマッピングされたら便利ですね。

Jacksonが提供する JsonDeserializer を拡張定義することで、独自のマッピングを実装することができます。これを利用し、リクエストボディマッピング用クラス FluitRequest に型としてFluitCategory を持つプロパティを定義できるようにします。


まず、列挙型 FluitCategory を以下のように整数型IDとの紐付けます。

// 各列挙型オブジェクトがidを持つ
@Getter
public enum FluitCategory {
    APPLE(1), ORANGE(2), GRAPE(3);

    private int id;

    private FluitCategory(int id) {
        this.id = id;
    }

    public static FluitCategory value(int id) {
        // idとマッチするFluitCategoryオブジェクトがない場合独自例外を送出
        return Arrays.stream(values()).filter(x -> x.id == id).findFirst().orElseThrow(() -> new HogeException());
    }
}

次に、以下のような JsonDeserializer 拡張クラスを定義します。 拡張する JsonDeserializerジェネリクス型には変換先の型である FluitCategory を指定しています。 変換元の値は deserialize メソッドの引数 jsonParser から取得できます。 今回は元々整数型の値であった category を変換したかったので、 getIntValue で値を取得します。この値を元に FluitCategory::value()FluitCategory に変換し返り値とします。


public class FluitCategoryDeserializer extends JsonDeserializer<FluitCategory> {

  @Override
  public FluitCategory deserialize(JsonParser jsonParser, DeserializationContext ctxt)
      throws IOException, JsonProcessingException {
    return FluitCategory.value(jsonParser.getIntValue());
  }

}

最後に、@Bean として以下のような ObjectMapper を定義します。 ここで定義されたObjectMapperがRequest BodyのJSONパース時に使用され、クライアントから整数型で送られたプロパティを FluitCategory 型プロパティとしてJava側で受け取った際に変換が実行されるようになります。

@Configuration
public class JsonConfiguration {

  @Bean
  public ObjectMapper jsonObjectMapper() {
    ObjectMapper mapper = new ObjectMapper();
    SimpleModule simpleModule = new SimpleModule();
    simpleModule.addDeserializer(FluitCategory.class, new FluitCategoryDeserializer());
    mapper.registerModule(simpleModule());
    return mapper;
  }