次のシナリオを想像してください。
class <T> Foo<T> {
....
}
class Bar {
Foo<Something> foo;
}
Foo用のカスタムジャクソンデシリアライザーを作成します。それを行うには(たとえば、Foo<Something>
プロパティを持つBar
クラスをデシリアライズするために)、デシリアライズ時にBar
で使用されるFoo<T>
の具体的なタイプを知る必要があります(たとえば、T
がSomething
であることを知る必要があります特定のケース)。
このようなデシリアライザーをどのように作成しますか?ジャクソンは型付きのコレクションとマップでそれを行うので、それを行うことが可能であるべきです。
説明:
問題の解決には2つの部分があるようです。
1)foo
内のプロパティBar
の宣言された型を取得し、それを使用してFoo<Somehting>
を逆シリアル化します
2)ステップ1を正常に完了するために、クラスfoo
内のプロパティBar
をデシリアライズしていることをデシリアライズ時に確認します
1と2をどのように完了するのですか?
JsonDeserializer
も実装するジェネリック型に対して、カスタム ContextualDeserializer
を実装できます。
たとえば、ジェネリック値を含む次の単純なラッパータイプがあるとします。
public static class Wrapper<T> {
public T value;
}
次のようなJSONを逆シリアル化します。
{
"name": "Alice",
"age": 37
}
次のようなクラスのインスタンスに:
public static class Person {
public Wrapper<String> name;
public Wrapper<Integer> age;
}
ContextualDeserializer
を実装すると、フィールドのジェネリック型パラメーターに基づいて、Person
クラスの各フィールドに特定のデシリアライザーを作成できます。これにより、名前を文字列として、年齢を整数としてデシリアライズできます。
完全なデシリアライザーは次のようになります。
public static class WrapperDeserializer extends JsonDeserializer<Wrapper<?>> implements ContextualDeserializer {
private JavaType valueType;
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
JavaType wrapperType = property.getType();
JavaType valueType = wrapperType.containedType(0);
WrapperDeserializer deserializer = new WrapperDeserializer();
deserializer.valueType = valueType;
return deserializer;
}
@Override
public Wrapper<?> deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException {
Wrapper<?> wrapper = new Wrapper<>();
wrapper.value = ctxt.readValue(parser, valueType);
return wrapper;
}
}
ジャクソンによって最初に呼び出されるので、ここで最初にcreateContextual
を見るのが最善です。 BeanProperty
からフィールドのタイプを読み取ります(例:Wrapper<String>
)そして、最初のジェネリック型パラメーターを抽出します(例:String
)。次に、新しいデシリアライザーを作成し、内部型をvalueType
として保存します。
この新しく作成されたデシリアライザでdeserialize
が呼び出されると、値をラッパー型全体としてではなく内部型としてデシリアライズするようにJacksonに要求し、デシリアライズされた値を含む新しいWrapper
を返すことができます。
このカスタムデシリアライザーを登録するには、それを含むモジュールを作成し、そのモジュールを登録する必要があります。
SimpleModule module = new SimpleModule()
.addDeserializer(Wrapper.class, new WrapperDeserializer());
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(module);
次に、上記のサンプルJSONをデシリアライズしようとすると、期待どおりに機能することがわかります。
Person person = objectMapper.readValue(json, Person.class);
System.out.println(person.name.value); // prints Alice
System.out.println(person.age.value); // prints 37
Jackson documentation には、コンテキストデシリアライザーの動作に関する詳細がいくつかあります。
ターゲット自体がジェネリック型の場合、プロパティはnullになります。そのため、DeserializationContextからvalueTtypeを取得する必要があります。
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
if (property == null) { // context is generic
JMapToListParser parser = new JMapToListParser();
parser.valueType = ctxt.getContextualType().containedType(0);
return parser;
} else { // property is generic
JavaType wrapperType = property.getType();
JavaType valueType = wrapperType.containedType(0);
JMapToListParser parser = new JMapToListParser();
parser.valueType = valueType;
return parser;
}
}