[Java]JacksonでModelMapperの代用品とする方法


# はじめに
今回はJacksonをModelMapperの代用品とするための方法です。
Jacksonは使えるけども、ModelMapperは使用できない特殊な状況時に役立ちます。

通常、こういったことが必要な場面に遭遇しないと思いますが、今僕がいる現場が正にJacksonは使えるますが、ModelMapperが使用できない状態にあります。

限られたライブラリでModelMapper並のマッピングができないかを考えていたところ、この方法に辿り着きました。


# ModelMapperをJacksonで代用する方法

## 前提条件

以降のソースは、lombokが導入されている前提で記述します。
https://projectlombok.org/


## 概説

使い勝手をよくするために、ObjectMapperのラッパークラスを定義します。
エラーハンドリングは省略しているので、適宜追加してください。

```java:JacksonModelMapper.java

public class JacksonModelMapper {

    private final ObjectMapper objectMapper = new ObjectMapper();

    public <T1, T2> map(T1 obj1, Class<T2> clazz) {
        try {
            String str = objectMapper.writeValueAsString(obj1);
            return objectMapper.readValue(str, clazz);
        } catch (IOExcepetion e) {
            // ignore
            return null;
        }
    }
}
```

モデルマッピングの仕組みは以下の通りです。

1. Model1を文字列（JSON）に変換
1. JSONに変換した文字列をModel2に変換

モデルを一度JSONにして別のモデルに変換するので、処理的には冗長です。
パフォーマンスを測定しているわけではありませんが、速度がシビアに求められる場合は導入は避けた方が良いでしょう。

## Setterありのモデルのマッピング

変換先のモデルにSetterが存在する場合は、以下の通りです。

```java:Model.java

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class Model {
    private Integer id;
    private String name;
    private String organizationCode;
    private String organizationName;
}

```

```java:Model2.java
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class Model2 {
    private Integer id;
    private String name;
    private String organizationCode;
    private String postCode;
}

```


```java:Main.java

public class Main {
    public void execute() {
        Model1 model1 = new Model();
        model1.setId(1);
        model1.setName("hrk-okd");
        model1.setOrganizationCode("000001");
        model1.setOrganizationName("ritsuan");

        JacksonModelMapper mapper = new JacksonModelMapper();
        Model2 model2 = mapper.map(model1, Model2.class);
    }
}

```

シンプルな形で実装されていると思います。

`@JsonIgnoreProperties(ignoreUnknown = true)` は、マッピング先にプロパティが存在しない場合もエラーとしないために必要です。
上記の場合、Model2に存在しないorganizationNameをマッピングする際に、エラーとするか処理を続行するかを制御できます。

## 完全コンストラクタモデルのマッピング

変換先のモデルが、完全コンストラクタのみのImmutableの場合は一工夫必要です。

- コンストラクタに `@JsonCreator` を追加
- コンストラクタの引数に `@JsonProperty("hogehoge")` を追加


下記を参考にしました。
https://www.366service.com/jp/qa/edd9ec25c79a01ec00724754bb257d11


```java:Model.java

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class Model {
    private Integer id;
    private String name;
    private String organizationCode;
    private String organizationName;
}

```

```java:Model2.java
@Getter
@JsonIgnoreProperties(ignoreUnknown = true)
public class Model2 {
    private final Integer id;
    private final String name;
    private final String organizationCode;
    private final String postCode;

    @JsonCreator
    public Model2(@JsonProperty("id") final Integer id,
        @JsonProperty("name") final String name,
        @JsonProperty("organizationCode") final String organizationCode,
        @JsonProperty("postCode") final String postCode) {
        this.id = id;
        this.name = name;
        this.organizationCode= organizationCode;
        this.postCode= postCode;
    }
}

```


```java:Main.java

public class Main {
    public void execute() {
        Model1 model1 = new Model();
        model1.setId(1);
        model1.setName("hrk-okd");
        model1.setOrganizationCode("000001");
        model1.setOrganizationName("ritsuan");

        JacksonModelMapper mapper = new JacksonModelMapper();
        Model2 model2 = mapper.map(model1, Model2.class);
    }
}

```

# 最後に

マッピングを行うライブラリは、getter、setterの存在するプロパティにしか対応していないこと多いです。
しかしながら、この方法を使用すれば完全コンストラクタのモデルにも対応できます。
デメリットは、余計なアノテーションを付けないといけないことですが、他に方法が無い場合は試してみても良いでしょう。

# 参考
- ModelMapper
http://modelmapper.org/

- Jackson
https://github.com/FasterXML/jackson-docs