Spring Boot のバージョンアップをした結果、Jackson のバージョンがあがり、Kotlin の Data クラスとインピーダンスミスマッチが発生しました

# 概要
Spring Boot のバージョンアップに伴い、下記のエンドポイントのレスポンスデータに、isActive のプロパティが含まれなくなりました。

```.kt
@RestController
@RequestMapping("/users")
class UserController {

    @GetMapping("/{id}")
    fun get(@PathVariable id: String) = User(
         userId = id,
         isActive = "1"
    )
}

data class User(
    val userId: String,
    val isActive: String
)
```

私は、レスポンス生成時でしか現象を体験していませんが、理屈上、おそらく、リクエスト時も同じ挙動になると思われます (Bean に格納してて欲しいリクエストパラメーターが欠落)。
フレームワーク側での対処策は特にないので、本コードを修正しないとどうにもならないですが、一助になることができれば幸いです。
そもそもネーミングルールから外れているという話ではありますが、副作用ではありますので。。

# 環境
Jackson のバージョンは、Spring Boot で指定されているバージョンです。

### バージョンアップ前
```
Spring Boot 2.2.6
Jackson 2.10.3
```

### バージョンアップ後

```
Spring Boot 2.5.6
Jackson 2.12.5
```

# 対処策
Jackson の挙動が変わったことで、String を返す、名前が "is" から始まるプロパティを Kotlin のデータクラスに作ると、Jackson は、該当のプロパティを無視するようです。プロパティの型を、Boolean 型にするか、あるいは、String 型はそのままにして、is をプロパティ名から削除して、<PropertyName> という名前に変更します。

## 原因
Kotlin のデータクラスは、val で定義した場合、中間コード生成時、getter を自動で生成します。
上述のクラスの場合、IntelliJ でデコンパイルすると、

```.java
public final class User {
   @NotNull
   private final String userId;
   @NotNull
   private final String isActive;

   @NotNull
   public final String getUserId() {
      return this.userId;
   }

   @NotNull
   public final String isActive() {
       return this.isActive
   }
}
```

のようなコードができます。どうも、Kotlin は、機械的に、通常は、プロパティ名に、getをつけて、getter を生成させていますが、is で始まる場合は、そのままの名前で getter を作っているようです。(getIsExclusive にしない) これが Jackson 側では 2.11 からまずくなったようです。
Spring Boot の挙動だと思うのですが、どうも、アプリケーション起動時に、Jackson は、Bean (リクエスト/レスポンスのデシリアライズ 対象となるクラス) のシグネチャをキャッシュするようです。シグネチャをキャッシュする際、

* フィールド
* ゲッター

をデシリアライズ対象のメンバだという解釈をしているのですが、String を返す、is から始まるプロパティは、フィールド扱いにも、ゲッター扱いにもなりません。
なぜかというと、

https://github.com/FasterXML/jackson-databind/blob/2.14/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java#L541

```.java
            final PropertyName implNameP = _propNameFromSimple(implName);
            // [databind#2527: Field-based renaming can be applied early (here),
            // or at a later point, but probably must be done before pruning
            // final fields. So let's do it early here
            final PropertyName rename = ai.findRenameByField(_config, f, implNameP);
            if ((rename != null) && !rename.equals(implNameP)) {
                if (_fieldRenameMappings == null) {
                    _fieldRenameMappings = new HashMap<>();
                }
                _fieldRenameMappings.put(rename, implNameP);
            }
```

の部分でキャッシュする際、findRenameByField の実装で、

https://github.com/FasterXML/jackson-databind/blob/71797dc2f5947c793ab4d4c4d02e54d78b7f0809/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationIntrospectorPair.java#L535

```.java
    @Override // since 2.11
    public PropertyName findRenameByField(MapperConfig<?> config,
            AnnotatedField f, PropertyName implName) {
        PropertyName n = _secondary.findRenameByField(config, f, implName);
        if (n == null) {
            n = _primary.findRenameByField(config, f, implName);
        }
        return n;
    }
```

となにかプロパティの名前を変更して、その結果、isActive が、active というフィールド名として想定され、isActive と、active は違う名前なので、キャッシュするフィールドではない、となりデシリアライズ対象にならない、、、という感じのようです。なにを意図してこうなっているのかはよくわかりませんが、ともかくそうなっています。(GitHub は、2.14 ですが、2.12.5 で、デバッグしても同じようなコードになっていました)

上記のような動きになるので、String の型をどうしても維持したい、、という場合は、

```.kt
data class User(
    val userId: String,
    val isactive: String
)
```

とキャメルケースをやめて `getIsactive` という、ゲッターを Kotlin に生成してもらったり、

```.kt
data class User(
    val userId: String,
    val isActive: String

    fun getIsActive(): String = this.isActive
)
```

というゲッターを自力で作って、Jackson にゲッターとして認識してもらったりします。
また、Java ではこの現象が発生する可能性は Kotlin より低くなります。

```.java
@Data
public class User {
   String userId;

   String isActive;
}
```

と Lombok でやれば、getIsActive のゲッターができます。
ただ、なにも考えないで、愚直に書いてしまったとしたら、

```.java
public class User {
   String userId;

   String isActive;

   public String getUserId() {
       return this.userId;
   }

   public String getIsActive() {
       return this.isActive;
   }
}
```

とやるかもしれないし、

```.java
public class User {
   String userId;

   String isActive;

   public String getUserId() {
       return this.userId;
   }

   public String isActive() {
       return this.isActive;
   }
}
```
とやるかもしれないので、実装者のセンスに依存します。
なお、pom.xml などで、Jackson を exclusion で、自分で指定という方法もありますが、せっかく Spring Boot がテストしてくれたものに違うバージョンをいれるのはむしろ危険性が上がる気がしますし、また、管理が煩雑になって、何のバージョンつかわれてるんだっけ、となる可能性もある気がするので、あまりおすすめはできないと思います。

## 所感
Jackson がなにを考えてこの実装を追加したのかはよくわかりません。
今までずっと安定して稼働していたコードに副作用を与えることには意味がないような気すらします。
しかし、慣習を遵守するのは重要であると思います。
is なのに、文字列を返すような getter は書かないのほうがよいのだと思います。
ただ、外界 (アプリケーション外) からのデータは、String でもらわないと、true/false 以外の値がリクエストされるとなにもしないとマッピング時に落ちてしまうので、そこをケアしてあげることをわすれないといけないですが。