Обработка альтернативных имен свойств в десериализации Jackson
Я работаю над преобразованием службы REST/JSON из Coldfusion 9 в приложение Spring-MVC 3.1. Я использую Jackson (1.9.5) и MappingJacksonJsonConverter, который предоставляет Spring, и настраиваю ObjectMapper для имен полей с CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES.
проблема, с которой я сталкиваюсь, заключается в том, что наш устаревший сервис производит "верблюжий регистр в верхнем регистре с подчеркиваниями" как имена свойств json. Потребители этого JSON, также написанного в ColdFusion, мог бы меньше заботиться о случае, но Джексон действительно заботится о случае и бросает непризнанные Propertyexceptions.
после просмотра почти всех настроек, которые я мог достичь из ObjectMapper-DeserializationConfig, DeserializerProvider и т. д., Я закончил с очень грязным Хак, в котором я анализирую дерево JSON, выводил его с помощью пользовательского JsonGenerator, который в нижнем регистре имена полей, а затем анализирует его обратно как объект.
MappingJacksonHttpMessageConverter mc = new MappingJacksonHttpMessageConverter() {
@Override
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return this.getObjectMapper().readValue(translateToLowerCaseKeys(inputMessage.getBody()), getJavaType(clazz));
}
private byte[] translateToLowerCaseKeys(InputStream messageBody) throws IOException {
StringWriter sw = new StringWriter();
JsonGenerator lowerCaseFieldNameGenerator = new JsonGeneratorDelegate(this.getObjectMapper().getJsonFactory().createJsonGenerator(sw)) {
@Override
public void writeFieldName(String name) throws IOException, org.codehaus.jackson.JsonGenerationException {
delegate.writeFieldName(name.toLowerCase());
};
};
this.getObjectMapper().writeTree(lowerCaseFieldNameGenerator, this.getObjectMapper().readTree(messageBody));
lowerCaseFieldNameGenerator.close();
return sw.getBuffer().toString().getBytes();
}
};
такое решение кажется очень неэффективным. Есть решение это работает для ключей карты, но я не смог найти аналогичное решение для имен полей.
альтернативным решением является наличие двух сеттеров, один из которых аннотируется именем устаревшего поля. Стратегия именования должна быть расширена, чтобы игнорировать эти поля, что в моей ситуации нормально, так как сопоставитель объектов не будет иметь дело с любыми другими классами со стратегией UPPER_UNDERSCORE:
public class JsonNamingTest {
public static class CaseInsensitive extends LowerCaseWithUnderscoresStrategy {
public String translate(String in) {
return (in.toUpperCase().equals(in) ? in : super.translate(in));
}
}
public static class A {
private String testField;
public String getTestField() {
return testField;
}
public void setTestField(String field) {
this.testField = field;
}
@JsonProperty("TEST_FIELD")
public void setFieldAlternate(String field) {
this.testField = field;
}
}
@Test
public void something() throws Exception {
A test = new A();
test.setTestField("test");
ObjectMapper mapper = new ObjectMapper().setPropertyNamingStrategy(new CaseInsensitive());
assertEquals("{"test_field":"test"}", mapper.writeValueAsString(test));
assertEquals("test", mapper.readValue("{"test_field":"test"}", A.class).getTestField());
assertEquals("test", mapper.readValue("{"TEST_FIELD":"test"}", A.class).getTestField());
}
}
это более идеально, чем предыдущий решение, но потребуется два аннотированных сеттера для каждого поля - один для нового формата, один для поддержки устаревшего формата.
кто-нибудь нашел способ сделать Джексона нечувствительным к регистру для десериализации имен полей или, если на то пошло, принять несколько псевдонимов для имени Поля?
4 ответов
Это было исправлено по состоянию на Jackson 2.5.0.
ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
вы можете использовать PropertyNamingStrategy:
class CaseInsensitiveNaming extends PropertyNamingStrategy {
@Override
public String nameForGetterMethod(MapperConfig<?> config,
AnnotatedMethod method, String defaultName)
{
// case-insensitive, underscores etc. mapping to property names
// so you need to implement the convert method in way you need.
return convert(defaultName);
}
}
objectMapper.setPropertyNamingStrategy(new CaseInsensitiveNaming());
Так как вы используете Jackson 1.X и не Джексон 2.x, вы можете использовать миксины. Из того, что я понимаю, вы хотите, чтобы десериализация понимала UPPER_CASE, но вы хотите сериализовать в lower_case. Добавьте MixIn в конфигурацию десериализации, но не в конфигурацию сериализации, как таковую:
public class JsonNamingTest {
public static class A {
private String testField;
public String getTestField() {
return testField;
}
public void setTestField(String field) {
this.testField = field;
}
}
public static class MixInA {
@JsonProperty("TEST_FIELD")
public void setTestField(String field) {}
}
@Test
public void something() throws Exception {
A test = new A();
test.setTestField("test");
ObjectMapper mapper = new ObjectMapper();
mapper.getDeserializationConfig().addMixInAnnotations(A.class, MixInA.class);
assertEquals("{\"test_field\":\"test\"}", mapper.writeValueAsString(test));
assertEquals("test", mapper.readValue("{\"test_field\":\"test\"}", A.class).getTestField());
assertEquals("test", mapper.readValue("{\"TEST_FIELD\":\"test\"}", A.class).getTestField());
}
}
однако вы не можете сделать это с Jackson 2.x: mixIns совместно используются конфигурацией сериализации и десериализации. Я еще не нашел способ справиться с псевдонимами с Jackson 2.x: (
при использовании Spring Boot также можно использовать Jackson2ObjectMapperBuilderCustomizer который сохраняет автоматическую конфигурацию по умолчанию. Это можно сделать, добавив Боб:
@Bean
public Jackson2ObjectMapperBuilderCustomizer acceptCaseInsensitive() {
return builder -> builder.featuresToEnable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
}