Как игнорировать запись списка, вызывающую ошибку десериализации в Джексоне?

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

public class RootLevelDTO {
   
    private List<ComplexEntry> complexEntries;
    // ... other fields, not relevant

}

Теперь у ComplexEntry могут быть подтипы, у них есть свойства типов enum и т. д. Здесь многое может пойти не так, если другая сторона связи обновит свой API и, например, добавляет еще один подтип или добавляет литерал перечисления.

Что я хотел бы сделать, так это сказать Джексону:

  • если вы столкнулись с какой-либо ошибкой привязки данных во время десериализации поля complexEntries...
  • ... не не выдавать исключение, а вместо этого игнорировать эту запись и переходить к следующей.

До сих пор я пытался использовать делегирующий десериализатор для ComplexEntry:

public class ComplexEntryDeserializer extends StdDeserializer<ComplexEntry> {

    private StdDeserializer<ComplexEntry> delegate;

    public ComplexEntryDeserializer(StdDeserializer<ComplexEntry> delegate){
        this.delegate = delegate;
    }

    public ComplexEntry deserialize(JsonParser p, DeserializationContext ctxt){
        try {
            return this.delegate.deserialize(p, ctxt);
        }catch(Exception e){
            // the list entry failed to deserialize, but we have to return *something* here
            return null;
        }
    }
    
    // ... other mandatory methods, not relevant here
}

У этого решения есть проблема, заключающаяся в том, что оно вводит значения null в список complexEntries, от которых я затем должен явно избавиться с помощью Converter.

Есть ли более элегантное решение этой проблемы?


person Alan47    schedule 24.08.2020    source источник
comment
Это читается как проблема XY, то есть вы выбрали неправильное решение и идете неправильным путем. Похоже, вам действительно нужно сказать Джексону: если вы обнаружите какие-либо неизвестные свойства, просто запишите их в общий Map<String, Object>. Тогда синтаксический анализ никогда не даст сбоев, и все (неизвестные) данные будут проанализированы правильно. См., например. 3.3. @JsonAnySetter в статье «Примеры аннотаций Джексона» в Baeldung.   -  person Andreas    schedule 24.08.2020
comment
Общие объекты @Andreas не будут работать. Поля в моих DTO имеют четко определенные типы Java (объекты, перечисления, списки). Джексон не может пытаться присвоить какое-либо значение полю перечисления, кроме литерала этого перечисления - JVM не позволит этого, и это правильно.   -  person Alan47    schedule 24.08.2020
comment
Пожалуйста, сделайте шаг назад и подумайте о самой проблеме: если API будет обновлен, вы действительно захотите (молча) игнорировать любые неизвестные типы или свойства? Разве они не будут актуальны в какой-то момент? Кроме того, кто является владельцем API? Это ты или другая сторона?   -  person Thomas    schedule 24.08.2020
comment
Да, именно это я и собираюсь сделать. Речь идет не о неизвестных свойствах определенных объектов; Джексон может легко их игнорировать через настройки. Я имею дело с неизвестными подтипами и неизвестными литералами перечислений. Я не хочу, чтобы Джексон завис, когда это произойдет, он должен просто игнорировать запись в списке.   -  person Alan47    schedule 24.08.2020
comment
Что ж, если вам действительно нужно это сделать, вы можете попробовать предоставить оформленный окончательный список в своем dto, который игнорирует нули. Если я правильно помню, только предоставление геттера для такого списка должно заставить Джексона использовать этот экземпляр для добавления десериализованных элементов.   -  person Thomas    schedule 24.08.2020
comment
Если ваш код должен обрабатывать будущие данные (изящно), то он должен быть разработан для этого. Например. если API указывает тип ENUM, т. е. фиксированный список строковых значений, но будущие версии могут содержать новые значения, то на самом деле это не тип ENUM, а просто строка с (текущим) списком известных значений. Сделайте так, чтобы ваш DTO использовал строковое поле, а затем напишите код, обеспечивающий хорошо объясненную обработку ошибок при обнаружении неизвестного значения, например, когда вы используете оператор switch и помещаете обработку ошибок в предложение default. Ошибка может объяснить, какое поле имеет неизвестное значение.   -  person Andreas    schedule 24.08.2020


Ответы (1)


После долгих возни я пришел к следующему решению. Он не требует никаких дополнительных модулей Джексона или другой магии, только один (конкретный) десериализатор.

ДТО:

public class RootLevelDTO {

    // use a custom deserializer for the list
    @JsonDeserialize(using = ListOfComplexEntryDeserializer.class)
    private List<ComplexEntry> complexEntries;

}

Десериализатор:

public class ListOfComplexEntryDeserializer extends JsonDeserializer<List<ComplexEntry>> {

    @Override
    public List<ComplexEntry> deserialize(JsonParser p, DeserializationContext ctxt) {
        List<ComplexEntry> resultList = new ArrayList<>();
        while(p.nextToken() != JsonToken.END_ARRAY){
            try {
                // delegate the deserialization of the individual list entries to the standard deserializers
                resultList.add(ctxt.readValue(p, ComplexEntry.class))
            }catch(Exception e){
                // log that the entry wasn't deserialized properly
                System.out.println("ComplexEntry could not be read and will be ignored.");
            }
        }
        return resultList;
    }

}

Большая оговорка: хотя приведенный выше код работает, это не то, к чему вы должны стремиться. Я действительно стою здесь спиной к стене и у меня нет другого выбора (из-за внешних факторов, не зависящих от меня), и в этом случае это работает.

person Alan47    schedule 24.08.2020
comment
Похожее решение опубликовано здесь одним из парней Джексона: stackoverflow.com/a/9097834/5209935 - основное отличие они сделали его универсальным. - person Matthew; 06.03.2021