Pracuję nad konwersją usługi REST/JSON z Coldfusion 9 na aplikację Spring-MVC 3.1. Używam Jackson (1.9.5) i MappingJacksonJsonConverter, który zapewnia Spring, i dostosowuję ObjectMapper do nadawania nazw polom CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES.Obsługa alternatywnych nazw właściwości w Jackson Deserialization
Problem, który napotykam, polega na tym, że nasza starsza usługa tworzy "przypadek wielbłąda do przypadku GÓRNEGO z podkreśleniami" jako nazwy właściwości json. Konsumenci tego JSON-a, również napisanego w ColdFusion, mogliby mniej przejmować się sprawą, ale Jackson dba o sprawę i rzuca UnrecognizedPropertyExceptions.
Po przeanalizowaniu prawie każdego ustawienia, które mogłem osiągnąć z ObjectMapper - DeserializationConfig, DeserializerProvider, itp., Skończyło się bardzo nieuporządkowanym hackowaniem, w którym parsowałem do drzewa JSON, wyprowadzałem je z niestandardowym JsonGeneratorem, który zmniejsza liczbę przypadków nazwy pól, a następnie przetwarza je ponownie jako obiekt.
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();
}
};
To rozwiązanie wydaje się bardzo nieefektywne. Istnieje solution, który działa dla kluczy mapy, ale nie mogłem znaleźć podobnego rozwiązania dla nazw pól.
Alternatywnym rozwiązaniem jest posiadanie dwóch seterów, z których jeden jest opatrzony nazwą starszego pola. Strategia nazewnictwa musi zostać rozszerzony, aby zignorować te pola, które w mojej sytuacji jest w porządku, ponieważ odwzorowujący obiekt nie będzie mieć do czynienia z innymi klasami ze strategią 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());
}
}
To jest bardziej idealny niż poprzedni rozwiązanie, ale wymagałoby dwóch ustawionych za pomocą adnotacji seterów dla każdego pola - jednego dla nowego formatu, jednego dla obsługi dotychczasowego formatu.
Czy ktoś natknął się na sposób uniezależnienia Jackson'a od deserializacji nazw pól, czy też w związku z tym akceptuje wiele aliasów dla nazwy pola?
'mapper.enable (MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);' powinien również działać. – Daniel