2012-04-09 10 views
11

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?

Odpowiedz

12

ta została ustalona jako Jackson 2.5.0.

ObjectMapper mapper = new ObjectMapper(); 
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true); 
+1

'mapper.enable (MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);' powinien również działać. – Daniel

6

Można użyć 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()); 
+0

Dzięki za sugestię próbowałem, ale to nie rozwiązuje problemu problem wydaje się być to, że strategię nazewnictwa. jest jednym ze sposobów, biorąc pod uwagę POJO z niektórymi polami, ustawiaczami, pobierającymi i konstruktorami, które mogą mieć adnotacje na nich, konwertuj je na nazwy pól json. Pole/metoda/konstruktor może odwzorować tylko na jedną nazwę pola json. –

+0

Ponadto właściwość PropertyNamingStrategy jest stosowana po adnotacjach.Więc gdybym miał zanotować dwie setery, jedną @JsonProperty ("test_field") i inną @JsonProperty ("TEST_FIELD"), konwersja małej litery spowoduje zderzenie tych dwóch setterów, co spowoduje wyjątek. –

5

Ponieważ używasz Jackson 1.x oraz 2.x nie Jackson, można użyć wstawek. Z tego co rozumiem, chcesz deserializacji zrozumieć UPPER_CASE, ale chcesz serializować w lower_case. Dodaj wstawek w config deserializacji ale nie w config serializacji, takich jak:

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()); 
    } 
} 

Jednak nie można tego zrobić z Jackson 2.x: wstawek są dzielone przez serializacji i deserializacji config. Jeszcze muszę znaleźć sposób, aby obsłużyć aliasów z Jacksonem 2.x :(

1

Podczas korzystania Wiosna Boot to również możliwe wykorzystanie Jackson2ObjectMapperBuilderCustomizer ., która zachowuje domyślny automatycznej konfiguracji można to zrobić poprzez dodanie bean.

@Bean 
public Jackson2ObjectMapperBuilderCustomizer acceptCaseInsensitive() { 
    return builder -> builder.featuresToEnable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); 
}