在使用Jackson进行反序列化时,如何放松命名策略?

时间:2015-11-13 10:39:47

标签: java json jackson fasterxml jackson-modules

我一直在尝试升级JSON模块以使用Jackson的FasterXML(2.6.3)版本而不是旧的Codehaus模块。在升级过程中,我注意到使用FasterXML而不是Codehaus时命名策略有所不同。

Codehaus在命名策略方面更灵活。下面的测试突出了我与FasterXML面临的问题。如何配置ObjectMapper所以它遵循Codehaus的相同策略?

我无法改变JSONProperty注释,因为有数百个注释。我希望升级在命名策略方面向后兼容。

import java.io.IOException;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
/*import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.PropertyNamingStrategy;*/
import org.junit.Assert;
import org.junit.Test;

public class JSONTest extends Assert {

    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class Product {

        @JsonProperty(value = "variationId")
        private String variantId;

        @JsonProperty(value = "price_text")
        private String priceText;

        @JsonProperty(value = "listPrice")
        public String listPrice;

        @JsonProperty(value = "PRODUCT_NAME")
        public String name;

        @JsonProperty(value = "Product_Desc")
        public String description;
    }

    private static final String VALID_PRODUCT_JSON =
            "{ \"list_price\": 289," +
             " \"price_text\": \"269.00\"," +
             " \"variation_id\": \"EUR\"," +
             " \"product_name\": \"Product\"," +
             " \"product_desc\": \"Test\"" +
            "}";

    @Test
    public void testDeserialization() throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);

        Product product = mapper.readValue(VALID_PRODUCT_JSON, Product.class);
        System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(product));
        assertNotNull(product.listPrice);
        assertNotNull(product.variantId);
        assertNotNull(product.priceText);
        assertNotNull(product.name);
        assertNotNull(product.description);
    }
}

2 个答案:

答案 0 :(得分:8)

自版本in fasterxml起,

@JsonProperty会覆盖任何PropertyNamingStrategy 2.4.0。但是,尚未发布的版本2.7.0将提供feature,以允许您选择重新使用旧行为。还有一个未实现的suggestion可以在每个注释级别进行切换,但这对您没有帮助。

Codehaus在映射时确实在PropertyNamingStrategy值之上应用@JsonProperty,但我找不到任何明确的文档。这似乎也是2.4.0之前的fastxml中的行为。 Here是另一个注意到行为相同差异的例子。

答案 1 :(得分:1)

虽然SkinnyJ提供的解决方案非常适合您的问题,但如果您不能等到2.7发布,您可以应用以下黑客来解决问题。

我们的想法是转换传入的JSON以匹配bean定义中的属性。下面的代码就是这样。应注意以下几点:

  1. 如果您正在处理嵌套结构,则必须实现递归函数来实现此转换。
  2. 进行转换需要一点开销。
  3. 代码:

    public class JSONTest extends Assert {
    
        @JsonIgnoreProperties(ignoreUnknown = true)
        public static class Product {
    
            @JsonProperty(value = "variationId")
            private String variantId;
    
            @JsonProperty(value = "price_text")
            private String priceText;
    
            @JsonProperty(value = "listPrice")
            public String listPrice;
    
            @JsonProperty(value = "PRODUCT_NAME")
            public String name;
    
            @JsonProperty(value = "Product_Desc")
            public String description;
        }
    
        private static final String VALID_PRODUCT_JSON =
                "{ \"list_price\": 289," +
                 " \"price_text\": \"269.00\"," +
                 " \"variation_id\": \"EUR\"," +
                 " \"product_name\": \"Product\"," +
                 " \"product_desc\": \"Test\"" +
                "}";
    
        @Test
        public void testDeserialization() throws IOException {
            ObjectMapper mapper = new ObjectMapper();
            mapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
    
            //Capture the original JSON in org.json.JSONObject
            JSONObject obj = new JSONObject(VALID_PRODUCT_JSON);
            JSONArray keys = obj.names();
    
            //New json object to be created using property names defined in bean
            JSONObject matchingJson = new JSONObject();
    
            //Map of lowercased key to original keys in incoming json. eg: Prod_id > prodid
            Map<String, String> jsonMappings = new LinkedHashMap<String, String>();
            for (int i = 0; i < keys.length(); i++) {
                String key = lowerCaseWithoutUnderScore(keys.getString(i));
                String value = keys.getString(i);
                jsonMappings.put(key, value);
            }
    
            /*
             * Iternate all jsonproperty beans and create new json
             * such that keys in json map to that defined in bean
             */
            Field[] fields = Product.class.getDeclaredFields();
            for (Field field : fields) {
                JsonProperty prop = field.getAnnotation(JsonProperty.class);
                String propNameInBean = prop.value();
                String keyToLook = lowerCaseWithoutUnderScore(propNameInBean);
                String keyInJson = jsonMappings.get(keyToLook);
                matchingJson.put(propNameInBean, obj.get(keyInJson));
            }
    
            String json = matchingJson.toString();
            System.out.println(json);
    
            //Pass the matching json to Object mapper
            Product product = mapper.readValue(json, Product.class);
            System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(product));
            assertNotNull(product.listPrice);
            assertNotNull(product.variantId);
            assertNotNull(product.priceText);
            assertNotNull(product.name);
            assertNotNull(product.description);
        }
    
        private String lowerCaseWithoutUnderScore(String key){
            return key.replaceAll("_", "").toLowerCase();
        }
    
    }