我正在努力将REST / JSON服务从Coldfusion 9转换为Spring-MVC 3.1应用程序。我正在使用Jackson(1.9.5)和Spring提供的MappingJacksonJsonConverter,并使用CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES自定义ObjectMapper以命名字段。
我面临的问题是,我们的遗留服务产生了“带有下划线的UPPER案例的骆驼案例”作为json属性名称。这个JSON的消费者,也是用ColdFusion编写的,可能不关心案例,但Jackson确实关心案例,并抛出UnrecognizedPropertyExceptions。
在查看了我可以从ObjectMapper - DeserializationConfig,DeserializerProvider等获得的每一个设置之后,我最终得到了一个非常混乱的hack,我解析为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();
}
};
此解决方案效率非常低。有solution适用于地图的键,但我无法找到类似的字段名称解决方案。
另一种解决方案是拥有两个setter,一个用传统字段名称注释。命名策略必须扩展为忽略这些字段,这在我的情况下很好,因为对象映射器不会处理任何其他具有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());
}
}
这比以前的解决方案更理想,但每个字段需要两个带注释的setter - 一个用于新格式,一个用于支持旧格式。
有没有人遇到过让杰克逊对反序列化字段名称不区分大小写的方法,或者对于字段名称接受多个别名?
答案 0 :(得分:12)
自Jackson 2.5.0起已修复。
ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
答案 1 :(得分:6)
您可以使用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());
答案 2 :(得分:5)
由于您使用的是Jackson 1.x而不是Jackson 2.x,因此您可以使用MixIns。根据我的理解,您希望反序列化能够理解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的别名:(
答案 3 :(得分:1)
使用Spring Boot时,也可以使用保留默认自动配置的Jackson2ObjectMapperBuilderCustomizer。这可以通过添加bean来完成:
@Bean
public Jackson2ObjectMapperBuilderCustomizer acceptCaseInsensitive() {
return builder -> builder.featuresToEnable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
}