SpringBootのREST APIでRequestBodyでの受け取り方を調べてみる

# 環境
Java8
SpringBoot 2.1.9.RELEASE

# 前提と共有
- Jacksonでデシリアライズを行うが、基本Setterは使わない方針
- Jacksonでデシリアライズを行う場合、オブジェクトに定義したフィールド可視性がpublicな場合`Getter/Setter`は不必要で、protected以下の場合は必要だが、今回はpublicで定義しない方針
- 出来るだけイミュータブルを目指す

# サンプルコード

### Stringのフィールド定義のみ
送信JSON: `{"id": "1", "name": "名前"}`

```java
/**
 * Getterで書く
 */
public class Sample {

    String id;
    String name;

    public String getId() {
        return id;
    }

    public String getName() {
        return name;
    }
}
```

```java
/**
 * コンストラクタで書く
 */
public class Sample {

    String id;
    String name;

    public Sample(String id, String name) {
        this.id = id;
        this.name = name;
    }
}
```

### intのフィールド定義のみ
送信JSON: `{"id": 1, "age": 20}`

```java
/**
 * Getterで書く
 */
public class Sample {

    int id;
    int age;

    public int getId() {
        return id;
    }

    public int getAge() {
        return age;
    }
}
```
```java
/**
 * コンストラクタで書く
 */
public class Sample {

    int id;
    int age;

    public Sample(int id, int age) {
        this.id = id;
        this.age = age;
    }
}
```

ちなみにInteger型を使用すると、null許容ができます(憤怒)
`{"id": null, "age": 20}`

```java
public class Sample {

    Integer id;
    int age;

    public Sample(Integer id, int age) {
        this.id = id;
        this.age = age;
    }
}
```
結果: `IntSample{id=null, age=20}`

### booleanのフィールド定義のみ
__Getterで実装することはできなかった__
なのでコンストラクタで書くやり方のみ
送信JSON: `{"isHoge": true, "isFuga": true}`

```java
/**
 * コンストラクタで書く
 */
public class BooleanSample {

    boolean isHoge;
    boolean isFuga;

    public BooleanSample(boolean isHoge, boolean isFuga) {
        this.isHoge = isHoge;
        this.isFuga = isFuga;
    }
}
```

Boolean型の場合、デシリアライズできない場合は`null`になる

### String、int、boolean全部のせ定義
String、int、booleanと検証したが、booleanが混ざるとコンストラクタが必須になるようなので、`Getter`での書き方は検討できない。よってコンストラクタの定義しかできない模様。

一応Getterでやってみた
送信JSON: `{"name": "きぐり", "age": 24, "isPerson": true}`

```java
/**
 * Getterで書く
 */
public class Sample {

    String name;
    int age;
    boolean isPerson;

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public boolean isPerson() {
        return isPerson;
    }
}
```
結果: `Sample{name='きぐり', age=24, isPerson=false}`
booleanの値がバインドされず非常に残念な結果です...。

```java
/**
 * コンストラクタで書く
 */
public class Sample {

    String name;
    int age;
    boolean isPerson;

    public Sample(String name, int age, boolean isPerson) {
        this.name = name;
        this.age = age;
        this.isPerson = isPerson;
    }
}
```
結果: `SIBSample{name='きぐり', age=24, isPerson=true}`
にっこりです。

# 罠だと思うところ(未解決)
今までのサンプルコードは共通してフィールド定義を2つ用意してきたことにお気づきでしょうか。このフィールド定義が1つ且つコンストラクタでデシリアライズする場合には、本来の使用の仕方とは違いますが、`@JsonCreator`と`@JsonProperty`を使用しないと例外になってしまう。

### 例外パターン
送信JSON: `{"name": "きぐり"}`

```java
public class Sample {

    String name;

    public Sample(String name) {
        this.name = name;
    }
}
```

#### 例外内容
```java
{
    "timestamp": "2019-10-11T03:25:50.724+0000",
    "status": 400,
    "error": "Bad Request",
    "message": "JSON parse error: Cannot construct instance of `sandbox.Sample` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `sandbox.Sample` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)\n at [Source: (PushbackInputStream); line: 1, column: 2]",
    "path": "/s"
}
```

### 成功パターン
送信JSON: `{"name": "きぐり"}`

```java
public class Sample {

    String name;

    @JsonCreator
    public Sample(@JsonProperty("name") String name) {
        this.name = name;
    }
}
```

# まとめ
完全にJacksonの説明をしていた、タイトル詐欺にならないかなこれ。
あと罠の部分、詳しい人誰か教えて下さい。
「コンストラクタなど使わなくとも、`@JsonProperty`をフィールドに付与すればいいのでは？」と言われそうですが、インスタンス生成の方法は一つに絞る且つ見易さなどで僕はコンストラクタ引数につけてます。もっといい方法あれば募集中!！!