「こんにちは、相棒!」

「やあ、ディエゴ」

「これで JSON シリアル化の基本を学んだのですね?」

「『基本』って何のことですか?私はよく知っています!」

「なんとも世間知らずだ。半分も知らない。せいぜい10パーセントだ」

「冗談だよ。他に何かある?」

「オブジェクト階層の逆シリアル化 (ポリモーフィック逆シリアル化)、コレクションの逆シリアル化、その他たくさんあります。Jackson フレームワークは大きくて強力です。正直に言って、まだ表面をなぞり始めたにすぎません。」

「分かった、それではそれについて話してください – 私はすべての耳を持っています。」

「レッスンを重ねるごとに賢くなっていくのがとても楽しいです!」

「そうですね、お役に立てて光栄です、ロボットの友達!」

「準備はできていますか?それでは聞いてください。」

「すでに学習したように、アノテーションはシリアル化と逆シリアル化の両方に使用されます。実際には、シリアル化に必要な情報は逆シリアル化よりもはるかに少なくなります。たとえば、次のようになります。」

Javaクラス JSON
class Cat
{
 public String name = "missy";
 public Cat[] cats = new Cat[0];
}
{
 "name": "missy",
 "cats": []
}
class Cat
{
 public String name = "missy";
 public List cats = new ArrayList<Cat>();
}
{
 "name": "missy",
 "cats": []
}
class Cat
{
 public String name = "missy";
 public List cats = new LinkedList<Cat>();
}
{
 "name": "missy",
 "cats": []
}

「Array、ArrayList、LinkedList、その他のクラスのインスタンスは JSON 配列に変換されます。」

「しかし、JSON 配列を逆シリアル化する場合、ArrayList と LinkedList のどちらのオブジェクトを作成する必要がありますか?」

「そうです。クラス メンバーがインターフェイス (例: public List<Cat> cat ) の場合、どのオブジェクトがそれに帰属する必要がありますか?」

「フィールドに追加の注釈を追加したり、逆シリアル化中にターゲット クラスを明示的に指定したりできます。次の例を見てください。」

JSONからオブジェクトを変換する
public static void main(String[] args) throws IOException
{
 String jsonString = ""{\"name\":\"Missy\",\"cats\":[{\"name\":\"Timmy\"},{\"name\":\"Killer\"}]}"";
 StringReader reader = new StringReader(jsonString);
 ObjectMapper mapper = new ObjectMapper();
 Cat cat = mapper.readValue(reader, TypeFactory.collectionType(ArrayList.class, Cat.class));
}
オブジェクトが JSON から逆シリアル化されるクラス
@JsonAutoDetect
class Cat {
 public String name;
 public List&ltCat> cats = new ArrayList<>();
 Cat() {
 }
}

「言い換えれば、mapper . readValueメソッドの 2 番目のパラメーターを使用して、逆シリアル化中に使用するクラスのリストを渡すことができます。」

「気に入っています。これは便利です。そのため、JSON 配列を必要なもの (ArrayList または LinkedList) に逆シリアル化できます。

「アノテーションの使用についても言及しましたが、どうやって行うのですか?」

「簡単です。例えば:」

JSONからオブジェクトを変換する
public static void main(String[] args) throws IOException
{
 String jsonString = ""{\"name\":\"Missy\",\"cats\":[{\"name\":\"Timmy\"},{\"name\":\"Killer\"}]}"";
 StringReader reader = new StringReader(jsonString);

 ObjectMapper mapper = new ObjectMapper();

 Cat cat = mapper.readValue(reader, Cat.class);
}
オブジェクトが JSON から逆シリアル化されるクラス
@JsonAutoDetect
class Cat
{
 public String name;
 @JsonDeserialize(as = ArrayList.class, contentAs = Cat.class)
 public List&ltCat> cats = new ArrayList<>();
 Cat() {
 }
}

「使用する List インターフェイスの実装を示すために、注釈@JsonDeserialize(as = ArrayList.class, contentAs = Cat.class)を 5 行目に追加するだけです。」

「ああ、なるほど。本当に簡単なことですね。」

「しかし、それだけではありません。リストのデータ型もインターフェイスだと仮定します。あなたならどうしますか?」

「ここでも注釈を使いますか?」

「はい、同じです。パラメータのタイプを示すためにも使用できます。次のようにします。」

コレクションの種類 データ型の設定方法
リスト @JsonDeserialize(contentAs = ValueTypeImpl.class)
地図 @JsonDeserialize(keyAs = KeyTypeImpl.class)

「すごいですね! 予想できないさまざまな状況に対応するために、本当にたくさんの注釈が必要になります。」

「それだけではありません。そして、ここからが本題です。実際のプロジェクトでは、クラスは、事実上どこでも使用される同じ基本クラスまたはインターフェイスを継承することがよくあります。そして、そのようなクラスを含むデータ構造を逆シリアル化する必要があると想像してください。例えば:"

オブジェクトをJSONに変換する
public static void main(String[] args) throws IOException
{
 Cat cat = new Cat();
 cat.name = "Missy";
 cat.age = 5;

 Dog dog = new Dog();
 dog.name = "Killer";
 dog.age = 8;
 dog.owner = "Bill Jefferson";

 ArrayList<Pet> pets = new ArrayList<Pet>();
 pets.add(cat);
 pets.add(dog);

 StringWriter writer = new StringWriter();
 ObjectMapper mapper = new ObjectMapper();
 mapper.writeValue(writer, pets);
 System.out.println(writer.toString());
}
オブジェクトを JSON に変換するクラス
@JsonAutoDetect
class Pet
{
 public String name;
}

@JsonAutoDetect
class Cat extends Pet
{
 public int age;
}

@JsonAutoDetect
class Dog extends Pet
{
 public int age;
 public String owner;
}
シリアル化の結果と画面出力:
[
 { "name" : "Missy", "age" : 5},
 { "name" : "Killer", "age" : 8 , "owner" : "Bill Jeferson"}
]

連載結果に注目です。

「このデータは基本的に他のクラスのデータと区別できないため、このデータを Java オブジェクトに逆シリアル化することはできません。」

「いくつかの特徴があります。それは、Dog には owner フィールドがあるということです。」

「はい、ただし、このフィールドは null になるか、シリアル化中に完全にスキップされる可能性があります。」

「では、私たちが知っているアノテーションを使用してデータ型を指定することはできないでしょうか?」

「いいえ。逆シリアル化後は、1 つのコレクションにさまざまな Cat オブジェクトと Dog オブジェクト、および Pet から継承できる 12 個の他のクラスが含まれるはずです。」

「ここではいったい何ができるの?」

ここでは二つのものが使われています。

「まず、あるタイプを別のタイプから区別するために、特定のフィールドが選択されます。フィールドが存在しない場合は、作成されます。」

「第二に、«ポリモーフィック逆シリアル化» のプロセスを制御できる特別なアノテーションがあります。できることは次のとおりです。」

オブジェクトをJSONに変換する
public static void main(String[] args) throws IOException
{
 Cat cat = new Cat();
 cat.name = "Missy";
 cat.age = 5;

 Dog dog = new Dog();
 dog.name = "Killer";
 dog.age = 8;
 dog.owner = "Bill Jeferson";

 House house = new House();
 house.pets.add(dog);
 house.pets.add(cat);

 StringWriter writer = new StringWriter();
 ObjectMapper mapper = new ObjectMapper();
 mapper.writeValue(writer, house);
 System.out.println(writer.toString());
}
オブジェクトを JSON に変換するクラス
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = Cat.class, name = "cat"),
@JsonSubTypes.Type(value = Dog.class, name = "dog")
})
class Pet
{
 public String name;
}

class Cat extends Pet
{
 public int age;
}

class Dog extends Pet
{
 public int age;
 public String owner;
}

class House
{
 public List&ltPet> pets = new ArrayList<>();
}
シリアル化の結果と画面出力:
{
 "pets" : [
 {"type" : "dog", "name" : "Killer", "age" : 8, "owner" : "Bill Jeferson"},
 {"type" : "cat", "name" : "Missy", "age" : 5}
]
}

注釈を使用して、JSON 表現に、Catクラスの値catとDogクラスの値Dogを保持するtypeと呼ばれる特別なフィールドが含まれることを示します。この情報は、オブジェクトを適切に逆シリアル化するのに十分です。逆シリアル化中に、作成されるオブジェクトの型はtype フィールドの値によって決定されます。

「クラス名が type フィールドの値として使用されることがあります (例: «com.example.entity.Cat.class») が、これは良い習慣ではありません。JSON を受信する外部アプリケーションはどのようにしてクラスの名前を知るのでしょうか?私たちのクラスですか? さらに悪いことに、クラスの名前が変更されることがあります。特定のクラスを識別するには、何らかの一意の名前を使用することをお勧めします。」

「すごい! ため息。デシリアライゼーションがこれほど複雑だとは知りませんでした。そして、微調整できることがこれほどたくさんあるとは。」

「そうです。あなたにとってこれらは新しい概念ですが、これはあなたを天才プログラマーにするための実践的な知識です。」

「アミーゴはクールなプログラマーだ。クールだ!」

「わかった。行って休憩してね。」