Ваш десериализатор не работает должным образом.
Когда вы набираете windowOne , вы читаете имена следующих двух полей - "windowSecond"
и null
(так как мывне токенов) - вместо значений JsonNode
, которые вы прочитали.Когда сериализатор возвращается, Джексон видит, что больше нет токенов, и пропускает десериализацию windowSecond , потому что больше нет данных для потребления.
@Override
public Window deserialize(JsonParser jsonParser, DeserializationContext dc) throws IOException, JsonProcessingException {
JsonNode jsonNode = jsonParser.getCodec().readTree(jsonParser);
String field = jsonParser.nextFieldName();
String nextField = jsonParser.nextFieldName();
return new Window(field + nextField, jsonNode.getNodeType().toString());
}
Это можно увидеть, посмотрев навывод вашей программы-примера:
{
"windowOne": {
"value1": "windowSecondnull",
"value2": "OBJECT"
},
"windowSecond": null
}
(ваш репозиторий не содержит того же кода, который вы, кстати, разместили здесь).
Строки:
String field = jsonParser.nextFieldName();
String nextField = jsonParser.nextFieldName();
- это проблема, вы должны использовать JsonNode
, который вы прочитали, и он будет работать как положено:
@Override
public Window deserialize(JsonParser jsonParser, DeserializationContext dc) throws IOException, JsonProcessingException {
JsonNode jsonNode = jsonParser.getCodec().readTree(jsonParser);
String value1 = jsonNode.hasNonNull("value1") ? jsonNode.get("value1").asText() : null;
String value2 = jsonNode.hasNonNull("value2") ? jsonNode.get("value2").asText() : null;
return new Window(value1, value2);
}
Ответ:
{
"windowOne": {
"value1": "Testing 1",
"value2": "Testing 2"
},
"windowSecond": {
"value1": "Testing 1 1",
"value2": "Testing 1 2"
}
}
InГлубина Объяснение
Чтобы понять, что именно происходит в исходном коде, давайте взглянем упрощенно на то, что происходит в синтаксическом анализаторе JSON:
Построенный JsonNode
, который мы 're синтаксический анализ представляет собой следующий JSON:
{
"windowOne": {
"value1": "Testing 1",
"value2": "Testing 2"
},
"windowSecond": {
"value1": "Testing 1 1",
"value2": "Testing 1 2"
}
}
Анализатор токенизирует это, чтобы позволить нам работать с ним.Давайте представим токенизированное состояние этого как этот список токенов:
START_OBJECT
FIELD_NAME: "windowOne"
START_OBJECT
FIELD_NAME: "value1"
VALUE: "Testing 1"
FIELD_NAME: "value2"
VALUE: "Testing 2"
END_OBJECT
FIELD_NAME: "windowSecond"
START_OBJECT
FIELD_NAME: "value1"
VALUE: "Testing 1 1"
FIELD_NAME: "value2"
VALUE: "Testing 1 2"
END_OBJECT
Джексон проходит через эти токены, пытаясь построить из него автомобиль.Он находит START_OBJECT
, затем FIELD_NAME: "windowOne"
, который, как он знает, должен десериализироваться Window
с помощью CustomDeserialize
, поэтому он создает CustomDeserialize
и вызывает метод deserialize
.
Затем десериализатор вызывает JsonNode jsonNode = jsonParser.getCodec().readTree(jsonParser);
, который ожидает, что следующий токен будет START_OBJECT
токеном, и анализирует все до совпадения с токеном END_OBJECT
, возвращая его как JsonNode
.
. Это вернет JsonNode
, который представляет этоJSON:
{
"value1": "window 2 value 1",
"value2": "window 2 value 2"
}
А остальные токены в парсере будут:
FIELD_NAME: "windowSecond"
START_OBJECT
FIELD_NAME: "value1"
VALUE: "Testing 1 1"
FIELD_NAME: "value2"
VALUE: "Testing 1 2"
END_OBJECT
END_OBJECT
Затем вы вызываете String field = jsonParser.nextFieldName();
, что задокументировано как:
Метод, который выбирает следующий токен (как если бы он вызывал nextToken) и проверял, является ли он JsonToken.FIELD_NAME;если это так, возвращает то же самое, что и getCurrentName (), в противном случае null
Т.е. он потребляет FIELD_NAME: "windowSecond"
и возвращает "windowSecond"
.Затем вы вызываете его снова, но поскольку следующий токен START_OBJECT
, он возвращает ноль.
Теперь у нас есть
field = "windowSecond"
nextField = null
jsonNode.getNodeType().toString() = "OBJECT"
и оставшиеся токены:
FIELD_NAME: "value1"
VALUE: "Testing 1 1"
FIELD_NAME: "value2"
VALUE: "Testing 1 2"
END_OBJECT
END_OBJECT
Ваш десериализатор превращает это в Window
, передавая field + nextField
(="windowSecondnull"
) и jsonNode.getNodeType().toString
(="OBJECT"
), а затем возвращает, передавая управление анализатором обратно Джексону, который сначала устанавливает Car.value1
в окноВаш десериализатор вернулся, а затем продолжает синтаксический анализ.
Вот где это становится немного странным.После того как ваш десериализатор вернется, Джексон ожидает токен FIELD_NAME
, и, так как вы использовали токен START_OBJECT
, он получает один.Однако он получает FIELD_NAME: "value1"
, и поскольку Car
не имеет атрибутов с именами value1
и , вы настроили Джексона на игнорирование неизвестных свойств, он пропускает это поле и его значение и переходит к FIELD_NAME: "value2"
который вызывает то же поведение.
Теперь остальные токены выглядят так:
END_OBJECT
END_OBJECT
Следующий токен END_OBJECT
, который сигнализирует, что ваш Car
был должным образом десериализован, поэтому Джексонвозвращает.
Здесь следует отметить, что анализатор все еще имеет один оставшийся токен, последний END_OBJECT
, но, поскольку Джексон по умолчанию игнорирует оставшиеся токены , что не вызывает никаких ошибок.
Если вы хотите увидеть сбой, удалите строку mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
:
Нераспознанное поле «значение1» (класс com.example.demodeserializer.Car), не помеченное как игнорируемое(2 известных свойства: "windowSecond", "windowOne"])
Пользовательский десериализатор, который использует токены
Чтобы написать собственный десериализатор, который вызывает синтаксический анализатор несколькоТимЕсли нам нужно удалить строку JsonNode jsonNode = jsonParser.getCodec().readTree(jsonParser);
и обработать токены самостоятельно.
Мы можем сделать это следующим образом:
@Override
public Window deserialize(JsonParser jsonParser, DeserializationContext dc) throws IOException, JsonProcessingException {
// Assert that the current token is a START_OBJECT token
if (jsonParser.currentToken() != JsonToken.START_OBJECT) {
throw dc.wrongTokenException(jsonParser, Window.class, JsonToken.START_OBJECT, "Expected start of Window");
}
// Read the next two attributes with value and put them in a map
// Putting the attributes in a map means we ignore the order of the attributes
final Map<String, String> attributes = new HashMap<>();
attributes.put(jsonParser.nextFieldName(), jsonParser.nextTextValue());
attributes.put(jsonParser.nextFieldName(), jsonParser.nextTextValue());
// Assert that the next token is an END_OBJECT token
if (jsonParser.nextToken() != JsonToken.END_OBJECT) {
throw dc.wrongTokenException(jsonParser, Window.class, JsonToken.END_OBJECT, "Expected end of Window");
}
// Create a new window and return it
return new Window(attributes.get("value1"), attributes.get("value2"));
}