web-dev-qa-db-ja.com

ジャクソンでジェネリック型のカスタムデシリアライザーを作成する方法は?

次のシナリオを想像してください。

class <T> Foo<T> {
    ....
}

class Bar {
    Foo<Something> foo;
}

Foo用のカスタムジャクソンデシリアライザーを作成します。それを行うには(たとえば、Foo<Something>プロパティを持つBarクラスをデシリアライズするために)、デシリアライズ時にBarで使用されるFoo<T>の具体的なタイプを知る必要があります(たとえば、TSomethingであることを知る必要があります特定のケース)。

このようなデシリアライザーをどのように作成しますか?ジャクソンは型付きのコレクションとマップでそれを行うので、それを行うことが可能であるべきです。

説明:

問題の解決には2つの部分があるようです。

1)foo内のプロパティBarの宣言された型を取得し、それを使用してFoo<Somehting>を逆シリアル化します

2)ステップ1を正常に完了するために、クラスfoo内のプロパティBarをデシリアライズしていることをデシリアライズ時に確認します

1と2をどのように完了するのですか?

15
Kresimir Nesek

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 には、コンテキストデシリアライザーの動作に関する詳細がいくつかあります。

35
andersschuller

ターゲット自体がジェネリック型の場合、プロパティは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;
    }
}
2
Ahmed Mkaouar