Пользовательский десериализатор Джексона не может определить целевой класс

#java #json #jackson

#java #json #джексон

Вопрос:

В настоящее время у меня есть объект Join, для которого я пытаюсь написать пользовательский десериализатор. Мы сохраняем ключ базы данных и имя Java-класса объекта в JSON, и это выглядит примерно так:

 {
    "@class": "com.mysite.data.ExpertFor",
    "id": "expert-for-1",
    "leftId": "user-profile-1",
    "leftType": "com.mysite.data.UserProfile",
    "rightId": "hotel-1",
    "rightType": "com.mysite.data.Hotel"
}
  

Мы делаем это, чтобы нам не нужно было сохранять фактический объект в базе данных; скорее, мы извлекаем его при десериализации. Проблема в том, что мой пользовательский сериализатор не может видеть @class элемент в потоке JSON — следовательно, он не может правильно создать экземпляр необходимого объекта после считывания всех данных. Вот мой десериализатор:

 public Join deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
    Map<String, Object> values = new HashMap<String, Object>();

    // Loop through the JSON document and pull out all
    // the values and put them into the map
    JsonToken token = jp.nextToken();
    while (token != JsonToken.END_OBJECT) {
        logger.info("Non-data field: ["   token   "], name is ["   jp.getCurrentName()   "], text is ["   jp.getText()   "]");
        if (token == JsonToken.VALUE_STRING) {
            values.put(jp.getCurrentName(), jp.getText());
        }
        token = jp.nextToken();
    }         
    // remainder of code omitted for brevity
}    
  

Теперь строки Log4J, которые записываются в журналы, показывают все соответствующие элементы JSON, которые я ожидал бы, КРОМЕ @class элемента

 Data field: [VALUE_STRING], name is [id], text is [expert-for-1]
Data field: [VALUE_STRING], name is [leftId], text is [user-profile-1]
Data field: [VALUE_STRING], name is [rightId], text is [hotel-1]
Data field: [VALUE_STRING], name is [leftType], text is [com.mysite.data.UserProfile]
Data field: [VALUE_STRING], name is [rightType], text is [com.mysite.data.Hotel]
  

Я попытался добавить @JsonTypeInfo аннотацию к своему классу, например

 @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class") 
  

но, похоже, элемент «проглатывается» кодом, и я не знаю, как к нему добраться. И мне это нужно, потому что любые потомки Join должны быть десериализованы. Вот некоторая дополнительная информация об иерархии, в которой находится этот класс:

Добавление аннотации @TypeInfo было сделано только для того, чтобы попытаться посмотреть, что происходит. Я получаю такое же поведение, независимо от того, есть оно или нет. Что действительно странно, так это то, что мы используем ту же базовую функциональность в других классах, которым требуется пользовательская десериализация, и они отлично видят @class . Вот иерархия этого:

 @JsonAutoDetect(value = JsonMethod.NONE)
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
public abstract class TaggedObject implements Serializable {
...
}
  

и тогда у нас есть DataObject

 public abstract class DataObject extends TaggedObject {
...
}
  

и тогда у нас есть наш класс Join:

 @JsonSerialize(using = Join.Serializer.class)
@JsonDeserialize(using = Join.Deserializer.class)
public abstract class Join<L, R> extends DataObject {
...
}
  

Я бы подумал, что @JsonTypeInfo будет унаследован от TaggedObject, но здесь он не виден, тогда как он виден другими классами в той же иерархии.

У кого-нибудь есть идеи о том, как я могу захватить класс этого объекта, чтобы я мог создать соответствующий объект? Чего мне здесь не хватает?

Ответ №1:

Вы не должны пытаться вручную обрабатывать @class И объявлять его обрабатываемым Jackson с помощью @JsonTypeInfo — либо позвольте Jackson полностью обработать его (что он может и будет делать), либо удалите anntoation и обработайте все вручную.

Ответ №2:

используя модель дерева Джексона, следующий код выводит недостающие @class :

 ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(jsonString);
Iterator<String> iterator = root.fieldNames();
while (iterator.hasNext()) {
    String fieldName = iterator.next();
    String fieldValue = root.get(fieldName).asText();
    System.out.println("name is ["   fieldName   "], text is ["   fieldValue   "]");
}
  

Вывод:

 name is [@class], text is [com.mysite.data.ExpertFor]
name is [id], text is [expert-for-1]
name is [leftId], text is [user-profile-1]
name is [leftType], text is [com.mysite.data.UserProfile]
name is [rightId], text is [hotel-1]
name is [rightType], text is [com.mysite.data.Hotel]
  

Комментарии:

1. Хотя этот код может ответить на вопрос, предоставление дополнительного контекста относительно того, как и / или почему он решает проблему, улучшит долгосрочную ценность ответа.