Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling alternate property names in Jackson Deserialization

Tags:

java

json

jackson

I am working on converting a REST/JSON service from Coldfusion 9 to a Spring-MVC 3.1 application. I am using Jackson (1.9.5) and the MappingJacksonJsonConverter that Spring provides, and am customizing the ObjectMapper to name fields with CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES.

The problem I am facing is that our legacy service produces 'camel case to UPPER case with underscores' as json property names. The consumers of this JSON, also written in ColdFusion, could care less about case, but Jackson does care about case, and throws UnrecognizedPropertyExceptions.

After looking into just about every setting I could reach from ObjectMapper - DeserializationConfig, DeserializerProvider, etc, I ended up with a very messy hack in which I parse to a JSON tree, output it with a custom JsonGenerator that lower cases the field names, and then parses it back in as an object.

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

This solution seems very inefficient. There is a solution that works for keys of a map, but I was unable to find a similar solution for field names.

An alternative solution is to have two setters, one annotated with the legacy field name. The naming strategy has to be extended to ignore these fields, which in my situation is fine since the object mapper won't be dealing with any other classes with an UPPER_UNDERSCORE strategy:

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

This is more ideal than the previous solution, but would require two annotated setters for every field - one for the new format, one to support the legacy format.

Has anyone come across a way to make Jackson case-insensitive for deserializing field names, or for that matter accept multiple aliases for a field name?

like image 521
Dan Watt Avatar asked Apr 09 '12 20:04

Dan Watt


People also ask

How do I ignore properties in ObjectMapper?

ObjectMapper; ObjectMapper objectMapper = new ObjectMapper(); objectMapper. configure(DeserializationFeature. FAIL_ON_UNKNOWN_PROPERTIES, false); This will now ignore unknown properties for any JSON it's going to parse, You should only use this option if you can't annotate a class with @JsonIgnoreProperties annotation.

What is the difference between JsonProperty and JsonAlias?

@JsonProperty can change the visibility of logical property using its access element during serialization and deserialization of JSON. @JsonAlias defines one or more alternative names for a property to be accepted during deserialization.

What is JsonProperty annotation?

@JsonProperty is used to mark non-standard getter/setter method to be used with respect to json property.

How do I ignore JsonProperty?

To ignore individual properties, use the [JsonIgnore] attribute. You can specify conditional exclusion by setting the [JsonIgnore] attribute's Condition property. The JsonIgnoreCondition enum provides the following options: Always - The property is always ignored.


2 Answers

This has been fixed as of Jackson 2.5.0.

ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
like image 167
Dan Watt Avatar answered Oct 10 '22 02:10

Dan Watt


You can use 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());
like image 23
Eugene Retunsky Avatar answered Oct 10 '22 04:10

Eugene Retunsky