Обработка альтернативных имен свойств в десериализации Джексона

Я работаю над преобразованием службы REST/JSON из Coldfusion 9 в приложение Spring -MVC 3.1. Я использую Jackson (1.9.5) и MappingJacksonJsonConverter, которые Spring предоставляет, и настраиваю ObjectMapper, чтобы называть поля CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES.

Проблема, с которой я столкнулся, заключается в том, что наша устаревшая услуга создает "верблюжьей кейс для случая UPPER с символами подчеркивания" как имена свойств json. Потребители этого JSON, также написанные в ColdFusion, могут меньше заботиться о случае, но Джексон заботится о случае и бросает UnrecognizedPropertyExceptions.

Изучив все настройки, которые я мог бы получить от 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();
    }
};

Это решение кажется очень неэффективным. Существует решение, которое работает для ключей карты, но я не смог найти аналогичное решение для имен полей.

Альтернативное решение состоит в том, чтобы иметь два сеттера, один из которых аннотирован с именем прежнего поля. Стратегия именования должна быть расширена, чтобы игнорировать эти поля, которые в моей ситуации прекрасны, поскольку объект mapper не будет иметь дело с любыми другими классами с стратегией 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());
    }
}

Это более удобно, чем предыдущее решение, но для каждого поля потребуются два аннотированных сеттера - один для нового формата, один для поддержки устаревшего формата.

Есть ли у кого-нибудь способ сделать Джексона без учета регистра для десериализации имен полей или, если на то пошло, принимать множественные псевдонимы для имени поля?

Ответ 1

Это было исправлено с Jackson 2.5.0.

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

Ответ 2

Вы можете использовать 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());

Ответ 3

Поскольку вы используете 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: (

Ответ 4

При использовании Spring Boot также можно использовать Jackson2ObjectMapperBuilderCustomizer, который сохраняет автоконфигурированную по умолчанию. Это можно сделать, добавив bean:

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