2016-03-22 4 views
9

Представьте себе следующий сценарий:Как создать пользовательский десериализатор в Джексоне для универсального типа?

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

class Bar { 
    Foo<Something> foo; 
} 

Я хочу написать собственный Jackson десериализатор для Foo. Для этого (например, для десериализации класса Bar, который имеет свойство Foo<Something>), мне нужно знать конкретный тип Foo<T>, используемый в Bar, во время десериализации (например, мне нужно знать, что T - это Something в том особый случай).

Как написать такой десериализатор? Это должно быть возможно, так как Джексон делает это с типизированными коллекциями и картами.

Разъяснения:

Кажется, есть 2 части к решению задачи:

1) получать объявленный тип недвижимости foo внутри Bar и использовать его для десериализации Foo<Somehting>

2) Выясните на этапе десериализации, что мы десериализируем свойство foo внутри класса Bar, чтобы успешно выполнить шаг 1)

Как завершить 1 и 2?

+0

Вы можете использовать отражение, чтобы выяснить, что * объявлен * тип 'foo' -' Foo '. Вот и все. Я не знаю о Джексоне, чтобы сказать, что это средство для вас здесь. ['Поле # getGenericType'] (http://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Field.html#getGenericType--) – Radiodef

ответ

23

Вы можете реализовать пользовательский 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 вызывается этой вновь созданной десериализатор, можно просто попросить Джексона десериализовать значение в качестве внутреннего типа, а не весь тип обертки, и возвращает новый 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 

Есть еще некоторые подробности о том, как контекстная deserializers работать в Jackson documentation.

+1

Благодарим вас за этот пост. Невероятно полезно для достижения того, чего я хотел. Я сделал несколько изменений, которые работают с корневыми значениями, если кто-нибудь из Google появится. https://gist.github.com/darylteo/a7be65b539c0d8d3ca0de94d96763f33 –

+1

Спасибо, что действительно помогло . Контекстный тип сам по себе может быть общим, в этом случае свойство будет null, вам нужно создать JsonDeserializer, используя ctxt.getContextualType –

1

Если сама цель является обобщенным типом, то свойство будет нулевым, для этого вам нужно получить valueTtype от DeserializationContext:

@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; 
    } 
} 
+0

Для любой, получающий NPE от принятого ответа, это решение, вероятно, то, что вам нужно. Thankyou - я понятия не имею, сколько времени вы только что спасли меня. – Martin

Смежные вопросы