Как принудительно применять ACCEPT_SINGLE_VALUE_AS_ARRAY в процессе десериализации джексона с помощью аннотации

Есть ли способ использовать аннотацию для свойства List в классе, чтобы использовать ACCEPT_SINGLE_VALUE_AS_ARRAY в Jackson? Я использую Spring и получаю следующее исключение

Вложенное исключение - com.fasterxml.jackson.databind.JsonMappingException: невозможно десериализовать экземпляр java.util.ArrayList из токена VALUE_STRING.

Предположим, у меня есть класс, как показано ниже:

public class MyClass {

    private List < String > value;
}

И мои структуры JSON, как показано ниже:

Случай 1:

[{"operator": "in", "value": ["Active"], "property": "status"}]

случай 2:

[{"operator": "like", "value": "aba", "property": "desc"}]

Какую аннотацию я должен использовать, чтобы сообщить фреймворку, что я хочу, чтобы эти 2 случая рассматривались одинаково при десериализации.

ОБНОВЛЕНИЕ: я переместил обновления в ответ в этом посте для большей ясности.

Ответ 1

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

В результате, на основании ответа Михала Фокса, я решил свою проблему, создав собственный десериализатор. Его как показано ниже:

В моей собственности:

@JsonDeserialize(using = CustomStringDeserializer.class)
private List<String> value;

И мой Deserializer:

public class CustomStringDeserializer extends JsonDeserializer<List<String>>{

    @Override
    public List<String> deserialize(JsonParser p, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.enable(DeserializationFeature. ACCEPT_SINGLE_VALUE_AS_ARRAY);
        return mapper.readValue(p, List.class);
    }

}

Ответ 2

Вы можете использовать @JsonFormat аннотацию,

public class MyClass {

    @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
    private List < String > value;

}

Чтобы работать с этим, вам нужна версия Джексона min 2.7.0. Вы также можете использовать другие доступные функции JsonFormat

Для версии 2.6.x

@Autowired private ObjectMapper mapper;
//...

mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
  • Добавьте этот код в свой Initializer Class.
  • Или вы можете напрямую настроить Jackson в вашем Bean Configuration

Это решит проблему, но будет активировано для каждого процесса deserialization.

Ответ 3

Я бы создал пользовательский JsonDeserializer, где я имел бы дело с обоими случаями и комментирует значение класса класса с десериализатором.

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

public class StringListDeserializer extends JsonDeserializer<List<String>>{

    @Override
    public List<String> deserialize(JsonParser parser, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {

        List<String> ret = new ArrayList<>();

        ObjectCodec codec = parser.getCodec();
        TreeNode node = codec.readTree(parser);

        if (node.isArray()){
            for (JsonNode n : (ArrayNode)node){
                ret.add(n.asText());
            }
        } else if (node.isValueNode()){
            ret.add( ((JsonNode)node).asText() );
        }
        return ret;
    }
}

MyClass:

public class MyClass {
    .
    @JsonDeserialize(using = StringListDeserializer.class)
    private List<String> value;
    .
}

Надеюсь, что это поможет.

BTW: Если есть способ справиться с таким случаем только с аннотацией, я также хочу это знать.

Ответ 4

Если этот проект является проектом Spring, вы можете поместить это свойство в себя application.properties:

spring.jackson.deserialization.UNWRAP_SINGLE_VALUE_ARRAYS=true

Ответ 5

Лучший способ решить эту проблему при использовании RestTemplate.

import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
ObjectMapper objectMapper = new ObjectMapper()
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
            .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
MappingJackson2HttpMessageConverter jacksonMappingConverter 
              = new MappingJackson2HttpMessageConverter(objectMapper);
restTemplate.getMessageConverters().add(0, jacksonMappingConverter);

Для анализируемого элемента используйте аннотацию, которая может быть объектом или массивом, как указано ниже

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
public class ParentObject{
  @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
  @JsonProperty("InnerObject")
 private List<InnerObject> innerObject;

}

Если вы не хотите добавлять новый маппер в restTemplate, измените существующий для поддержки варианта использования

List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
        for (HttpMessageConverter<?> httpMessageConverter : messageConverters) {
            if (httpMessageConverter instanceof MappingJackson2HttpMessageConverter) {
                MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = (MappingJackson2HttpMessageConverter) httpMessageConverter;
                mappingJackson2HttpMessageConverter.getObjectMapper()
                        .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
            }
        }