LoginSignup
2
1

More than 1 year has passed since last update.

[java tips] lombok @Builderを使ったクラスでもjson fixtureを使いたい

Last updated at Posted at 2021-10-15

前提

  • Java
  • Spring boot
  • Jacksonを利用してobject <-> json変換
  • junitで単体テストを書く
  • lombokを利用している

概要

Javaでunittestを書くとき、テストで使うデータを用意するのはわりとめんどくさい。

var hoge = new Hoge("hogeId", "hogeName", new Fuga("fugaId", "fugaName", ...));

というわけで、一般的には外部にjsonファイルを用意し、これを読み込ませてテストに使う。

  • hoge.json
{
   "id": "hogeId",
   "name": "hogeName",
   "fuga": {
      "id": "fugaId",
      "name": "fugaName",
      ...
   }
}

しかし、一部のオブジェクト(たとえばドメインモデル)は「不変オブジェクト(immutable object)」にしないといけない。そういうコーディングルールはよくある。

@Builder
@Getter
public class Hoge {
   private final String id;
   private final String name;
   private final Fuga fuga;
}

これをObjectMapperを使って読み込むことができるか?

実装

もちろん可能。

@JsonDeserialize(builder=Hoge.HogeBuilder.class)
@Builder
@Getter
public class Hoge {
   private final String id;
   private final String name;
   private final Fuga fuga;

   @JsonPOJOBuilder(withPrefix="")
   public static class HogeBuilder {}
}

これで、

   @Test
   public void testHoge() throws IOException {
      var fixture = getClass().getResource("./hoge.json");
      var hoge = mapper.readValue(fixture, Hoge.class);

      ...
   }

いつも通りjson fixtureを使ったテストができる。

参照

テストのためにモデルにアノテーションを付与することがコーディングルール上許されるか?の問題は残るが・・・。

おおっと(余談)

話はこれで終わらないことが判明。

@Builder
@Getter
@JsonDeserialize(builder=Fuga.FugaBuilder.class)
public class Fuga {
   private final String id;
   private final String name;
   private final Hoge hoge;

   @JsonPOJOBuilder(withPrefix="")
   public static class FugaBuilder {}
}

@Builder
@Getter
// value object
public class Hoge {
   private final String hoge;
}

こういった「文字列を含むプリミティブ型一属性しかもたないValueObjectは、ObjectMapperは特別扱いする」ことが分かった。
こういったimmutableな一属性モデルを、ObjectMapperはデフォルトでDDDのvalue objectとして扱う。

{
   "id": "fugaId",
   "name": "fugafuga",
   "hoge": {
      "hoge": "hogehoge"
   }
}

これはエラーになり、

{
   "id": "fugaId",
   "name": "fugafuga",
   "hoge": "hogehoge"
}

これだと通る。こういうjson階層を無視するような動きをjacksonはDelegatingと言うらしい。つまり

@Builder
@Getter
@JsonDeserialize(builder=Fuga.FugaBuilder.class)
public class Fuga {
   private final String id;
   private final String name;
   private final Hoge hoge;

   @JsonPOJOBuilder(withPrefix="")
   public static class FugaBuilder {}
}

@Builder
@Getter
// value objectチックだが、primitiveじゃない
public class Hoge {
   private final Hogehoge hoge;
}

@Builder
@Getter
public class Hogehoge {
   private final String id;
   private final String name;
}

こんなモデルは

{
   "id": "fugaId",
   "name": "fugafuga",
   "hoge": {
      "hoge": {
         "id": "hogeId",
         "name": "hogeId"
      }
   }
}

こんなjsonになるが、delegatingしたいなら

@Builder
@Getter
@JsonDeserialize(builder=Fuga.FugaBuilder.class)
public class Fuga {
   private final String id;
   private final String name;
   private final Hoge hoge;

   @JsonPOJOBuilder(withPrefix="")
   public static class FugaBuilder {}
}

@Builder
@Getter
public class Hoge {
   private final Hogehoge hoge;

   // Delegatingを付与できるのが@JsonCreator以外見つからず。
   @JsonCreator(mode=JsonCreator.Mode.Delegating)
   public static Hoge newHoge(Hogehoge hogehoge){
      return Hoge.builder().hoge(hogehoge).build();
   }
}

@Builder
@Getter
@JsonDeserialize(builder=Hogehoge.HogehogeBuilder.class)
public class Hogehoge {
   private final String id;
   private final String name;

   @JsonPOJOBuilder(withPrefix="")
   public static class HogehogeBuilder {}
}

こんなコードを書いてあげるとjson階層が繰り上がって

{
   "id": "fugaId",
   "name": "fugafuga",
   "hoge": {
      "id": "hogeId",
      "name": "hogeId"
   }
}

こうなる。jacksonはまだまだ深い・・・・

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1