Мой класс CustomDeserializer не работает второй раз, который используется во втором поле того же класса - PullRequest
0 голосов
/ 07 декабря 2018

Я использую зависимости Джексона, думаю, проблема в том, когда jsonParser вызывается более трех раз.но я не уверен, почему так происходит ... У меня есть такой случай:

@Entity 
public class Car implements Serializable {

   @JsonDeserialize(using = CustomDeserialize.class)
   private Window windowOne:

   @JsonDeserialize(using = CustomDeserialize.class)
   private Window windowSecond:
   ....//Getters/Setters


}

Класс CustomDeserializer

public class CustomDeserializer extends StdDeserializer<Window> {

  .....  // constructors


@Override
public Window deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
    JsonNode jsonNode = jsonParser.getCodec().readTree(jsonParser);
    String field = jsonParser.nextFieldName();
    String nextField = jsonParser.nextFieldName();
      return new Window("value1", "valu2");
    }
}

Класс менеджера, который вызывает objectMapper

 public class Manager {

    private ObjectMapper mapper = new ObjectMapper();


    public void serializeCar(ObjectNode node) {
         // node comes loaded with valid values two windows of a Car.
         // All is OK until here, so this treeToValue enters to CustomDeserializer once only.
         // treeToValue just read the first window ?  because of second window is null and the first window enters on mode debug. 
         Car car = mapper.treeToValue(node, Car.class);
     }

 }

Когда я отлаживаю, я не знаю, почему treeToValue (objectNode, class) просто вызывает один раз класс CustomSerializer, а второй - не вызывает его.Пожалуйста, что здесь не так?или почему mapper.treeToValue игнорирует второе поле, используя CustomDeserializer?Заранее спасибо, эксперты.

ОБНОВЛЕНО

В качестве примера я добавил хранилище:

https://github.com/NextSoftTis/demo-deserializer

1 Ответ

0 голосов
/ 13 декабря 2018

Ваш десериализатор не работает должным образом.

Когда вы набираете 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"));
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...