Динамическая обработка полиморфного типа с помощью Jackson
У меня есть иерархия классов, подобная этой:
public static class BaseConfiguration {
}
public abstract class Base {
private BaseConfiguration configuration;
public String id;
public BaseConfiguration getConfiguration() { ... }
public void setConfiguration(BaseConfiguration configuration) { ... }
}
public class A extends Base {
public static class CustomConfigurationA extends BaseConfiguration {
String filename;
String encoding;
}
CustomConfigurationA getConfiguration() { ... }
}
class B extends Base {
public static class CustomConfigurationB extends BaseConfiguration {
/* ... */
}
CustomConfigurationB getConfiguration() { ... }
}
И вход json как этот (который я не могу изменить сам)
{
"id":"File1",
"configuration":{
"filename":"...",
"encoding":"UTF-8"
}
}
Я разбираю JSON на Java с Джексоном, как этот
ObjectMapper mapper = new ObjectMapper();
value = mapper.readValue(in, nodeType);
Я хочу десериализовать классы A, B и другие из JSON с помощью JAVA/Jackson. В JSON нет информации о типе (и не может быть). Я не могу использовать аннотации для классов (я их не владею), и я (верю), что я не могу использовать mixins, поскольку существует потенциально произвольное число классов, таких как A и B (и mixins не являются динамическими). Хорошо, что десериализующий код знает, какой правильный пользовательский класс использовать для десериализации (в основном есть известное отображение класса от класса к классу конфигурации), но я не знаю, как заставить Джексона распознавать эту информацию при десериализации JSON.
Вкратце: я хочу иметь возможность разрешить тип десериализации объекта конфигурации в зависимости от типа окружающего класса, задав все, что необходимо для ObjectMapper. Как это можно достичь?
Ответы
Ответ 1
По-видимому, ответ заключался в том, чтобы реализовать нечто похожее на шестое решение, размещенное в http://programmerbruce.blogspot.com/2011/05/deserialize-json-with-jackson-into.html, в котором используются уникальные имена элементов JSON для идентификации целевого тип для десериализации.
Ответ 2
Хороший ответ, предоставленный программистом Брюсом!
У меня есть случай полиморфизма, в котором я хочу, чтобы объекты домена были POJO и не использовали зависимости от аннотаций Джексона.
Поэтому я предпочитаю использовать пользовательский десериализатор и Factory для определения типа или для создания конкретных классов.
Вот мой код...
(помните, что у меня есть иерархия аннотаций, которые на самом деле являются "тегами пользователя", а не аннотациями Java).
Ниже приведен метод десериализации
public class AnnotationDeserializer extends StdDeserializer<Annotation> {
AnnotationDeserializer() {
super(Annotation.class);
}
@Override
public Annotation deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
ObjectNode root = (ObjectNode) mapper.readTree(jp);
Class<? extends Annotation> realClass = null;
Iterator<Entry<String, JsonNode>> elementsIterator = root.getFields();
while (elementsIterator.hasNext()) {
Entry<String, JsonNode> element = elementsIterator.next();
if ("type".equals(element.getKey())) {
realClass = AnnotationObjectFactory.getInstance()
.getAnnotationClass(element.getKey());
break;
}
}
if (realClass == null)
return null;
return mapper.readValue(root, realClass);
}
}
Ответ 3
Я должен был сделать что-то подобное, и в итоге создал родовой сериализатор полиморфных списков и десеризатор. Вот десериализатор, который, как я думаю, будет работать для вас:
public class PolymorphicListDeserializer extends JsonDeserializer<List<?>> implements ContextualDeserializer {
private HashMap<String, Class> _typeMap = null;
private Class _elementType;
private static <T> List<T> getNewList(Class<T> clazz) {
return new ArrayList<T>();
}
@Override
public List<?> deserialize(final JsonParser jp, DeserializationContext ctxt) throws IOException {
final List list = getNewList(_elementType);
JsonToken nextToken = jp.getCurrentToken();
if (nextToken == JsonToken.START_OBJECT) {
if ( _typeMap.containsKey( currentFieldName )) {
list.add( _elementType.cast( ctxt.readValue( jp, _typeMap.get( currentFieldName ) ) ) );
}
nextToken = jp.nextToken();
} else if (currentFieldName != null && isEndToken(nextToken) && wrapperCount == 0) {
break;
} else {
nextToken = jp.nextToken();
}
}
return list;
}
public JsonDeserializer<List<?>> createContextual( DeserializationContext ctxt, BeanProperty property ) throws JsonMappingException {
//In Jackson 2.6.3, this method is called once per instance and the exception is never thrown
if ( _typeMap == null )
_typeMap = new HashMap<String, Class>();
else
throw new RuntimeException("Unsupported version of Jackson. Code assumes context is created once and only once.");
_elementType = property.getType().getContentType().getRawClass();
//For now, requiring XmlElements annotation to work. May add support for JsonElements (if needed) later.
for (XmlElement e : property.getAnnotation(XmlElements.class).value()) {
_typeMap.put(e.name(), e.type());
}
return this;
}
private static boolean isStartToken(JsonToken t) {
boolean result = false;
if (t == JsonToken.START_OBJECT) {
result = true;
} else if (t == JsonToken.START_ARRAY) {
result = true;
}
return result;
}
Ответ 4
В вышеприведенных ответах описывается решение, однако не хватает того, что означают фактически используемые аннотации. Если вам интересно, что на самом деле делают эти аннотации, идея за ними и почему они требуются, пожалуйста, перейдите по ссылке ниже. В нем очень хорошо сказано. http://wiki.fasterxml.com/JacksonPolymorphicDeserialization