【问题标题】:Handling alternate property names in Jackson Deserialization在杰克逊反序列化中处理备用属性名称
【发布时间】:2012-04-22 04:54:15
【问题描述】:

我正在将 REST/JSON 服务从 Coldfusion 9 转换为 Spring-MVC 3.1 应用程序。我正在使用 Jackson (1.9.5) 和 Spring 提供的 MappingJacksonJsonConverter,并且正在自定义 ObjectMapper 以使用 CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES 命名字段。

我面临的问题是我们的旧服务将“驼峰式大小写为带有下划线的大写”作为 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());
    }
}

这比以前的解决方案更理想,但每个字段都需要两个带注释的设置器 - 一个用于新格式,一个用于支持旧格式。

有没有人发现让 Jackson 在反序列化字段名称时不区分大小写,或者为此接受字段名称的多个别名?

【问题讨论】:

    标签: java json jackson


    【解决方案1】:

    从 Jackson 2.5.0 开始,此问题已得到修复。

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

    【讨论】:

    • mapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); 也应该可以工作。
    【解决方案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());
    

    【讨论】:

    • 感谢您的建议。我已经尝试过了,但它不能解决问题。问题似乎是命名策略是一种方式。给定一个带有一些可能带有注释的字段、setter、getter 和构造函数的 POJO,将它们转换为 json 字段名称。一个字段/方法/构造函数只能映射到一个 json 字段名。
    • 此外,PropertyNamingStrategy 在任何注释之后应用。因此,如果我要注解两个 setter,一个 @JsonProperty("test_field") 和另一个 @JsonProperty("TEST_FIELD"),小写转换会导致这两个 setter 发生冲突,从而引发异常。
    【解决方案3】:

    由于您使用的是 Jackson 1.x 而不是 Jackson 2.x,因此您可以使用 MixIns。据我了解,您希望反序列化理解 UPPER_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 中做到这一点:mixIn 由序列化和反序列化配置共享。我还没有找到用 Jackson 2.x 处理别名的方法:(

    【讨论】:

      【解决方案4】:

      当使用 Spring Boot 时,也可以使用 Jackson2ObjectMapperBuilderCustomizer,它保留了默认的自动配置。这可以通过添加一个 bean 来完成:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-12-09
        • 2015-12-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-04-07
        • 1970-01-01
        • 2016-08-24
        相关资源
        最近更新 更多