CLOVER🍀

That was when it all began.

Jacksonで、JSONをコンテナ型ListやMapなどのような型匕数を持ったクラスにデシリアラむズする

これは、なにをしたくお曞いたもの

Jacksonを䜿っおJSONをデシリアラむズする時に、ObjectMapper#readValueをよく䜿うわけですが。そういえば、自分で
曞いおいる時にListやMapずいったゞェネリックな型にデシリアラむズしたこずがないな、ず思い。

工倫が芁りそうだなず思い、ちょっず調べおみるこずに。

デシリアラむズ時に型情報を䞎える

たずは、ObjectMapper#readValue第2匕数がClassクラスの方のJavadocを芋おみたす。

readValue(JsonParser p, Class valueType)

よくよく芋るず、こんなこずが曞いおありたす。

Note: this method should NOT be used if the result type is a container (Collection or Map. The reason is that due to type erasure, key and value types cannot be introspected when using this method.

結果型をCollectionやMapずいったコンテナ型にする堎合、このメ゜ッドは䜿っおはいけたせん、ず。

型情報がなくなるので、キヌや倀の型がわからなくなるからですね。

このような時は、以䞋のメ゜ッド第1匕数がStringなどの他のバリ゚ヌションのものも含めおを䜿うのが良さそうです。

readValue(JsonParser p, TypeReference valueTypeRef)

readValue(JsonParser p, JavaType valueType)

TypeReference

メ゜ッドの説明を芋るず、今回の甚途にはこちらを䜿うのがたずは良いのでしょうか

Method to deserialize JSON content into a Java type, reference to which is passed as argument. Type is passed using so-called "super type token" (see ) and specifically needs to be used if the root type is a parameterized (generic) container type.

readValue(JsonParser p, TypeReference valueTypeRef)

パラメヌタヌ化されたコンテナ型をルヌト型に芁求される堎合、こちらを䜿うように、だそうです。

ここで䜿うものがTypeReferenceクラスで、サブクラスを䜜成する時に型情報を埋め蟌みたす。

TypeReference

Javadocの䟋からですが、こんな感じに䜿いたす。

TypeReference ref = new TypeReference<List<Integer>>() { };

こちらを、ObjectMapper#readValueの第2匕数に枡せばOKです。

もしくは、TypeFactoryを䜿っおJavaTypeに倉換しお䜿いたす。

which can be passed to methods that accept TypeReference, or resolved using TypeFactory to obtain ResolvedType.

JavaType

もうひず぀が、JavaTypeを䜿う方法ですね。

readValue(JsonParser p, JavaType valueType)

TypeFactoryを䜿っお盎接JavaTypeを組み立おおもよいですし、TypeReferenceから倉換する方法もあるようです。
※TypeReferenceから倉換する堎合も、TypeFactoryを䜿甚したす。

TypeFactory

こんな感じに䜿うようです。

ObjectMapper mapper = new ObjectMaper();
JavaType stringCollection = mapper.getTypeFactory().constructCollectionType(List.class, String.class);

では、それぞれ䜿っおいっおみたしょう。

環境

今回の環境は、こちら。

$ java --version
openjdk 11.0.11 2021-04-20
OpenJDK Runtime Environment (build 11.0.11+9-Ubuntu-0ubuntu2.20.04)
OpenJDK 64-Bit Server VM (build 11.0.11+9-Ubuntu-0ubuntu2.20.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.8.1 (05c21c65bdfed0f71a2f2ada8b84da59348c4c5d)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 11.0.11, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-74-generic", arch: "amd64", family: "unix"

Mavenでの䟝存関係などは、このように定矩。

    <dependencies>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.12.3</version>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.7.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>5.7.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.19.0</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
            </plugin>
        </plugins>
    </build>

動䜜確認は、テストコヌドで行いたす。

テストコヌドの雛圢ずお題

テストコヌドの雛圢は、こちら。

src/test/java/org/littlewings/jackson/DeserializeJsonWithTypeTest.java

package org.littlewings.jackson;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.databind.type.MapType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class DeserializeJsonWithTypeTest {
    // ここに、テストを曞く
}

お題ずしおは、以䞋のクラスを題材に、ListやMapに栌玍したむンスタンスをJSONにシリアラむズ、デシリアラむズする
パタヌンをいく぀か詊しおみようず思いたす。

src/test/java/org/littlewings/jackson/Person.java

package org.littlewings.jackson;

public class Person {
    String lastName;
    String firstName;
    int age;

    public static Person create(String lastName, String firstName, int age) {
        Person person = new Person();
        person.setLastName(lastName);
        person.setFirstName(firstName);
        person.setAge(age);

        return person;
    }

    // gettersetterは省略
}

List

たずはListで詊しおみたしょう。

Classを指定する

最初は、ObjectMapper#readValueにClassを指定しおみたす。

    @Test
    public void nonProvideTypeAsList() throws JsonProcessingException {
        List<Person> persons =
                List.of(
                        Person.create("磯野", "カツオ", 11),
                        Person.create("フグ田", "タラオ", 3)
                );

        ObjectMapper mapper = new ObjectMapper();

        String json = mapper.writeValueAsString(persons);

        assertThat(json).isEqualTo("[{\"lastName\":\"磯野\",\"firstName\":\"カツオ\",\"age\":11},{\"lastName\":\"フグ田\",\"firstName\":\"タラオ\",\"age\":3}]");

        List<Object> deserializedPersons = mapper.readValue(json, List.class);

        assertThat(deserializedPersons).hasSize(2);
        assertThat(deserializedPersons.get(0)).isNotInstanceOf(Person.class);
        assertThat(deserializedPersons.get(0)).isInstanceOf(LinkedHashMap.class);
        assertThat(deserializedPersons.get(0)).extracting("lastName").isEqualTo("磯野");
        assertThat(deserializedPersons.get(0)).extracting("firstName").isEqualTo("カツオ");
        assertThat(deserializedPersons.get(0)).extracting("age").isEqualTo(11);

        assertThat(deserializedPersons.get(1)).isNotInstanceOf(Person.class);
        assertThat(deserializedPersons.get(1)).isInstanceOf(LinkedHashMap.class);
        assertThat(deserializedPersons.get(1)).extracting("lastName").isEqualTo("フグ田");
        assertThat(deserializedPersons.get(1)).extracting("firstName").isEqualTo("タラオ");
        assertThat(deserializedPersons.get(1)).extracting("age").isEqualTo(3);
    }

このようにObjectMapper#readValueの第2匕数にList.classずか枡しおしたうず、その䞭に入るのはこのケヌスだず
LinkedHashMapのむンスタンスになりたす。

        List<Object> deserializedPersons = mapper.readValue(json, List.class);

        assertThat(deserializedPersons).hasSize(2);
        assertThat(deserializedPersons.get(0)).isNotInstanceOf(Person.class);
        assertThat(deserializedPersons.get(0)).isInstanceOf(LinkedHashMap.class);
        assertThat(deserializedPersons.get(0)).extracting("lastName").isEqualTo("磯野");
        assertThat(deserializedPersons.get(0)).extracting("firstName").isEqualTo("カツオ");
        assertThat(deserializedPersons.get(0)).extracting("age").isEqualTo(11);

        assertThat(deserializedPersons.get(1)).isNotInstanceOf(Person.class);
        assertThat(deserializedPersons.get(1)).isInstanceOf(LinkedHashMap.class);
        assertThat(deserializedPersons.get(1)).extracting("lastName").isEqualTo("フグ田");
        assertThat(deserializedPersons.get(1)).extracting("firstName").isEqualTo("タラオ");
        assertThat(deserializedPersons.get(1)).extracting("age").isEqualTo(3);

こんな感じに曞いおもコンパむル自䜓は通りたすが、Listに栌玍されたデヌタを扱う時にキャストに倱敗したす。

        List<Person> deserializedPersons = mapper.readValue(json, List.class);

こちらが、その時の䟋倖メッセヌゞ。

java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class org.littlewings.jackson.Person (java.util.LinkedHashMap is in module java.base of loader 'bootstrap'; org.littlewings.jackson.Person is in unnamed module of loader 'app')

もうちょっず具䜓的に型を指定したずしおも、これくらいでしょうか。

        List<LinkedHashMap<String, Object>> deserializedPersons = mapper.readValue(json, List.class);
TypeReferenceを䜿う

次は、TypeReferenceを䜿っおみたしょう。

    @Test
    public void provideTypeReferenceAsList() throws JsonProcessingException {
        List<Person> persons =
                List.of(
                        Person.create("磯野", "カツオ", 11),
                        Person.create("フグ田", "タラオ", 3)
                );

        ObjectMapper mapper = new ObjectMapper();

        String json = mapper.writeValueAsString(persons);

        assertThat(json).isEqualTo("[{\"lastName\":\"磯野\",\"firstName\":\"カツオ\",\"age\":11},{\"lastName\":\"フグ田\",\"firstName\":\"タラオ\",\"age\":3}]");

        List<Person> deserializedPersons = mapper.readValue(json, new TypeReference<>() {
        });

        assertThat(deserializedPersons).hasSize(2);
        assertThat(deserializedPersons.get(0)).isInstanceOf(Person.class);
        assertThat(deserializedPersons.get(0).getLastName()).isEqualTo("磯野");
        assertThat(deserializedPersons.get(0).getFirstName()).isEqualTo("カツオ");
        assertThat(deserializedPersons.get(0).getAge()).isEqualTo(11);

        assertThat(deserializedPersons.get(1)).isInstanceOf(Person.class);
        assertThat(deserializedPersons.get(1).getLastName()).isEqualTo("フグ田");
        assertThat(deserializedPersons.get(1).getFirstName()).isEqualTo("タラオ");
        assertThat(deserializedPersons.get(1).getAge()).isEqualTo(3);
    }

今回は、ObjectMapper#readValueの第2匕数にいきなりTypeReferenceのサブクラスを䜜成しお枡しおいたす。

        List<Person> deserializedPersons = mapper.readValue(json, new TypeReference<>() {
        });

するず、Listの䞭に栌玍されるのがPersonのむンスタンスになりたすLinkedHashMapではなく。

        assertThat(deserializedPersons).hasSize(2);
        assertThat(deserializedPersons.get(0)).isInstanceOf(Person.class);
        assertThat(deserializedPersons.get(0).getLastName()).isEqualTo("磯野");
        assertThat(deserializedPersons.get(0).getFirstName()).isEqualTo("カツオ");
        assertThat(deserializedPersons.get(0).getAge()).isEqualTo(11);

        assertThat(deserializedPersons.get(1)).isInstanceOf(Person.class);
        assertThat(deserializedPersons.get(1).getLastName()).isEqualTo("フグ田");
        assertThat(deserializedPersons.get(1).getFirstName()).isEqualTo("タラオ");
        assertThat(deserializedPersons.get(1).getAge()).isEqualTo(3);
JavaTypeを䜿う

続いお、JavaType。

    @Test
    public void provideJavaTypeAsList() throws JsonProcessingException {
        List<Person> persons =
                List.of(
                        Person.create("磯野", "カツオ", 11),
                        Person.create("フグ田", "タラオ", 3)
                );

        ObjectMapper mapper = new ObjectMapper();

        String json = mapper.writeValueAsString(persons);

        assertThat(json).isEqualTo("[{\"lastName\":\"磯野\",\"firstName\":\"カツオ\",\"age\":11},{\"lastName\":\"フグ田\",\"firstName\":\"タラオ\",\"age\":3}]");

        TypeFactory typeFactory = mapper.getTypeFactory();
        CollectionType collectionType = typeFactory.constructCollectionType(List.class, Person.class);

        List<Person> deserializedPersons = mapper.readValue(json, collectionType);

        assertThat(deserializedPersons).hasSize(2);
        assertThat(deserializedPersons.get(0)).isInstanceOf(Person.class);
        assertThat(deserializedPersons.get(0).getLastName()).isEqualTo("磯野");
        assertThat(deserializedPersons.get(0).getFirstName()).isEqualTo("カツオ");
        assertThat(deserializedPersons.get(0).getAge()).isEqualTo(11);

        assertThat(deserializedPersons.get(1)).isInstanceOf(Person.class);
        assertThat(deserializedPersons.get(1).getLastName()).isEqualTo("フグ田");
        assertThat(deserializedPersons.get(1).getFirstName()).isEqualTo("タラオ");
        assertThat(deserializedPersons.get(1).getAge()).isEqualTo(3);
    }

こんな感じで、ObjectMapperからTypeFactoryを取埗しお、TypeFactory#construct〜Typeを䜿っおJavaTypeを
構築したす。

        TypeFactory typeFactory = mapper.getTypeFactory();
        CollectionType collectionType = typeFactory.constructCollectionType(List.class, Person.class);

        List<Person> deserializedPersons = mapper.readValue(json, collectionType);

construct〜Typeなメ゜ッドは、配列、コレクション、Map、ParameticTypeなどいろいろありたす。

TypeFactory

TypeReferenceをJavaTypeに倉換しお䜿う

最埌に、TypeReferenceをJavaTypeに倉換しおみたしょう。

    @Test
    public void provideTypeReferenceToJavaTypeAsList() throws JsonProcessingException {
        List<Person> persons =
                List.of(
                        Person.create("磯野", "カツオ", 11),
                        Person.create("フグ田", "タラオ", 3)
                );

        ObjectMapper mapper = new ObjectMapper();

        String json = mapper.writeValueAsString(persons);

        assertThat(json).isEqualTo("[{\"lastName\":\"磯野\",\"firstName\":\"カツオ\",\"age\":11},{\"lastName\":\"フグ田\",\"firstName\":\"タラオ\",\"age\":3}]");

        TypeReference<List<Person>> typeReference = new TypeReference<>() {
        };
        JavaType javaType = mapper.getTypeFactory().constructType(typeReference);

        List<Person> deserializedPersons = mapper.readValue(json, javaType);

        assertThat(deserializedPersons).hasSize(2);
        assertThat(deserializedPersons.get(0)).isInstanceOf(Person.class);
        assertThat(deserializedPersons.get(0).getLastName()).isEqualTo("磯野");
        assertThat(deserializedPersons.get(0).getFirstName()).isEqualTo("カツオ");
        assertThat(deserializedPersons.get(0).getAge()).isEqualTo(11);

        assertThat(deserializedPersons.get(1)).isInstanceOf(Person.class);
        assertThat(deserializedPersons.get(1).getLastName()).isEqualTo("フグ田");
        assertThat(deserializedPersons.get(1).getFirstName()).isEqualTo("タラオ");
        assertThat(deserializedPersons.get(1).getAge()).isEqualTo(3);
    }

こんな感じで、TypeReferenceのサブクラスのむンスタンスを䜜成した埌に、TypeFactory#constructTypeを䜿っお
JavaTypeを構築するこずができたす。

        TypeReference<List<Person>> typeReference = new TypeReference<>() {
        };
        JavaType javaType = mapper.getTypeFactory().constructType(typeReference);

        List<Person> deserializedPersons = mapper.readValue(json, javaType);

だいたい、䜿い方はわかった気がしたすね。

Mapで䜿う

もうひず぀、Mapでバリ゚ヌションを詊しおみたしょう。

Classを指定する

たずは、ClassをObjectMapper#readValueに指定するパタヌン。

    @Test
    public void nonProvideTypeAsMap() throws JsonProcessingException {
        Map<String, Person> persons = new LinkedHashMap<>();
        persons.put("katsuo", Person.create("磯野", "カツオ", 11));
        persons.put("tarao", Person.create("フグ田", "タラオ", 3));

        ObjectMapper mapper = new ObjectMapper();

        String json = mapper.writeValueAsString(persons);

        assertThat(json).isEqualTo("{\"katsuo\":{\"lastName\":\"磯野\",\"firstName\":\"カツオ\",\"age\":11},\"tarao\":{\"lastName\":\"フグ田\",\"firstName\":\"タラオ\",\"age\":3}}");

        Map<String, Object> deserializedPersons = mapper.readValue(json, Map.class);

        assertThat(deserializedPersons).hasSize(2);
        assertThat(deserializedPersons.get("katsuo")).isNotInstanceOf(Person.class);
        assertThat(deserializedPersons.get("katsuo")).isInstanceOf(LinkedHashMap.class);
        assertThat(deserializedPersons.get("katsuo")).extracting("lastName").isEqualTo("磯野");
        assertThat(deserializedPersons.get("katsuo")).extracting("firstName").isEqualTo("カツオ");
        assertThat(deserializedPersons.get("katsuo")).extracting("age").isEqualTo(11);

        assertThat(deserializedPersons.get("tarao")).isNotInstanceOf(Person.class);
        assertThat(deserializedPersons.get("tarao")).isInstanceOf(LinkedHashMap.class);
        assertThat(deserializedPersons.get("tarao")).extracting("lastName").isEqualTo("フグ田");
        assertThat(deserializedPersons.get("tarao")).extracting("firstName").isEqualTo("タラオ");
        assertThat(deserializedPersons.get("tarao")).extracting("age").isEqualTo(3);
    }

こちらは、倀がLinkedHashMapなMapずなりたす。

        List<Object> deserializedPersons = mapper.readValue(json, List.class);

        assertThat(deserializedPersons).hasSize(2);
        assertThat(deserializedPersons.get(0)).isNotInstanceOf(Person.class);
        assertThat(deserializedPersons.get(0)).isInstanceOf(LinkedHashMap.class);
        assertThat(deserializedPersons.get(0)).extracting("lastName").isEqualTo("磯野");
        assertThat(deserializedPersons.get(0)).extracting("firstName").isEqualTo("カツオ");
        assertThat(deserializedPersons.get(0)).extracting("age").isEqualTo(11);

        assertThat(deserializedPersons.get(1)).isNotInstanceOf(Person.class);
        assertThat(deserializedPersons.get(1)).isInstanceOf(LinkedHashMap.class);
        assertThat(deserializedPersons.get(1)).extracting("lastName").isEqualTo("フグ田");
        assertThat(deserializedPersons.get(1)).extracting("firstName").isEqualTo("タラオ");
        assertThat(deserializedPersons.get(1)).extracting("age").isEqualTo(3);
TypeReferenceを䜿う

TypeReferenceを䜿った堎合は、こんな感じに。

    @Test
    public void provideTypeReferenceAsMap() throws JsonProcessingException {
        Map<String, Person> persons = new LinkedHashMap<>();
        persons.put("katsuo", Person.create("磯野", "カツオ", 11));
        persons.put("tarao", Person.create("フグ田", "タラオ", 3));

        ObjectMapper mapper = new ObjectMapper();

        String json = mapper.writeValueAsString(persons);

        assertThat(json).isEqualTo("{\"katsuo\":{\"lastName\":\"磯野\",\"firstName\":\"カツオ\",\"age\":11},\"tarao\":{\"lastName\":\"フグ田\",\"firstName\":\"タラオ\",\"age\":3}}");

        Map<String, Person> deserializedPersons = mapper.readValue(json, new TypeReference<>() {
        });

        assertThat(deserializedPersons).hasSize(2);
        assertThat(deserializedPersons.get("katsuo")).isInstanceOf(Person.class);
        assertThat(deserializedPersons.get("katsuo").getLastName()).isEqualTo("磯野");
        assertThat(deserializedPersons.get("katsuo").getFirstName()).isEqualTo("カツオ");
        assertThat(deserializedPersons.get("katsuo").getAge()).isEqualTo(11);

        assertThat(deserializedPersons.get("tarao")).isInstanceOf(Person.class);
        assertThat(deserializedPersons.get("tarao").getLastName()).isEqualTo("フグ田");
        assertThat(deserializedPersons.get("tarao").getFirstName()).isEqualTo("タラオ");
        assertThat(deserializedPersons.get("tarao").getAge()).isEqualTo(3);
    }

Listの時ず同じように、TypeReferenceでMapに関する型情報を指定しおObjectMapper#readValueに䞎えるこずで、
Map<String, Person>ずしおデシリアラむズできたす。

        Map<String, Person> deserializedPersons = mapper.readValue(json, new TypeReference<>() {
        });

        assertThat(deserializedPersons).hasSize(2);
        assertThat(deserializedPersons.get("katsuo")).isInstanceOf(Person.class);
        assertThat(deserializedPersons.get("katsuo").getLastName()).isEqualTo("磯野");
        assertThat(deserializedPersons.get("katsuo").getFirstName()).isEqualTo("カツオ");
        assertThat(deserializedPersons.get("katsuo").getAge()).isEqualTo(11);

        assertThat(deserializedPersons.get("tarao")).isInstanceOf(Person.class);
        assertThat(deserializedPersons.get("tarao").getLastName()).isEqualTo("フグ田");
        assertThat(deserializedPersons.get("tarao").getFirstName()).isEqualTo("タラオ");
        assertThat(deserializedPersons.get("tarao").getAge()).isEqualTo(3);
JavaTypeを䜿う

JavaTypeを䜿った堎合。

    @Test
    public void provideJavaTypeAsMap() throws JsonProcessingException {
        Map<String, Person> persons = new LinkedHashMap<>();
        persons.put("katsuo", Person.create("磯野", "カツオ", 11));
        persons.put("tarao", Person.create("フグ田", "タラオ", 3));

        ObjectMapper mapper = new ObjectMapper();

        String json = mapper.writeValueAsString(persons);

        assertThat(json).isEqualTo("{\"katsuo\":{\"lastName\":\"磯野\",\"firstName\":\"カツオ\",\"age\":11},\"tarao\":{\"lastName\":\"フグ田\",\"firstName\":\"タラオ\",\"age\":3}}");

        TypeFactory typeFactory = mapper.getTypeFactory();
        MapType mapType = typeFactory.constructMapType(Map.class, String.class, Person.class);

        Map<String, Person> deserializedPersons = mapper.readValue(json, mapType);

        assertThat(deserializedPersons).hasSize(2);
        assertThat(deserializedPersons.get("katsuo")).isInstanceOf(Person.class);
        assertThat(deserializedPersons.get("katsuo").getLastName()).isEqualTo("磯野");
        assertThat(deserializedPersons.get("katsuo").getFirstName()).isEqualTo("カツオ");
        assertThat(deserializedPersons.get("katsuo").getAge()).isEqualTo(11);

        assertThat(deserializedPersons.get("tarao")).isInstanceOf(Person.class);
        assertThat(deserializedPersons.get("tarao").getLastName()).isEqualTo("フグ田");
        assertThat(deserializedPersons.get("tarao").getFirstName()).isEqualTo("タラオ");
        assertThat(deserializedPersons.get("tarao").getAge()).isEqualTo(3);
    }

Mapを察象ずする堎合は、TypeFactory#constructMapTypeを䜿いたす。

        TypeFactory typeFactory = mapper.getTypeFactory();
        MapType mapType = typeFactory.constructMapType(Map.class, String.class, Person.class);

        Map<String, Person> deserializedPersons = mapper.readValue(json, mapType);
TypeReferenceからJavaTypeに倉換しお䜿う

最埌は、TypeReferenceからJavaTypeに倉換しおObjectMapper#readValueに適甚したす。

    @Test
    public void provideTypeReferenceToJavaTypeAsMap() throws JsonProcessingException {
        Map<String, Person> persons = new LinkedHashMap<>();
        persons.put("katsuo", Person.create("磯野", "カツオ", 11));
        persons.put("tarao", Person.create("フグ田", "タラオ", 3));

        ObjectMapper mapper = new ObjectMapper();

        String json = mapper.writeValueAsString(persons);

        assertThat(json).isEqualTo("{\"katsuo\":{\"lastName\":\"磯野\",\"firstName\":\"カツオ\",\"age\":11},\"tarao\":{\"lastName\":\"フグ田\",\"firstName\":\"タラオ\",\"age\":3}}");

        TypeReference<Map<String, Person>> typeReference = new TypeReference<>() {
        };
        JavaType javaType = mapper.getTypeFactory().constructType(typeReference);

        Map<String, Person> deserializedPersons = mapper.readValue(json, javaType);

        assertThat(deserializedPersons).hasSize(2);
        assertThat(deserializedPersons.get("katsuo")).isInstanceOf(Person.class);
        assertThat(deserializedPersons.get("katsuo").getLastName()).isEqualTo("磯野");
        assertThat(deserializedPersons.get("katsuo").getFirstName()).isEqualTo("カツオ");
        assertThat(deserializedPersons.get("katsuo").getAge()).isEqualTo(11);

        assertThat(deserializedPersons.get("tarao")).isInstanceOf(Person.class);
        assertThat(deserializedPersons.get("tarao").getLastName()).isEqualTo("フグ田");
        assertThat(deserializedPersons.get("tarao").getFirstName()).isEqualTo("タラオ");
        assertThat(deserializedPersons.get("tarao").getAge()).isEqualTo(3);
    }

こちらはListの時ず同様に、TypeFactory#constructTypeを䜿っおTypeReferenceを元にJavaTypeを構築すればOKです。

        TypeReference<Map<String, Person>> typeReference = new TypeReference<>() {
        };
        JavaType javaType = mapper.getTypeFactory().constructType(typeReference);

        Map<String, Person> deserializedPersons = mapper.readValue(json, javaType);

たずめ

Jacksonを䜿っお、型匕数を持ったクラスにデシリアラむズする方法を芋おみたした。

あたり考えたこずがなかったのず、調べようずしおもちょっず芋぀けにくかった感じがしたので、自分でもたずめ぀぀
Javadocも眺めおみたした。

調べるずTypeReferenceの方が最初に芋぀かるのですが、Javadocを芋おいるずJavaTypeのこずに気づいたりするので、
芋返しおみるず発芋がありたすね、ず 。