使用Jackson反序列化时,如何放宽命名策略?

10

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

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

1
JSON区分大小写;不同大小写的键表示不同的内容。看起来Codehaus版本在这方面不合规。 - Thorn G
2个回答

10

@JsonProperty 从版本 2.4.0 开始覆盖了任何基于 PropertyNamingStrategy 的设置(在 fasterxml 中)。然而,尚未发布的版本 2.7.0 将提供一个 功能,使您可以选择恢复旧的行为。还有一个未实现的 建议 可以在每个注释级别上切换此设置,但这并不能真正帮助您。

似乎Codehaus在映射时确实会将PropertyNamingStrategy应用于@JsonProperty值之上,尽管我找不到任何清晰的文档。这在fasterxml 2.4.0之前也是该行为。此处是另一个注意到相同行为差异的示例。

2
尽管SkinnyJ提供的解决方案对您的问题非常完美,但如果您等不及2.7版本发布,您可以尝试下面的hack方法来解决问题。
思路是将传入的JSON转换为与bean定义中属性匹配的格式。以下代码实现了这一点。需要注意以下几点:
  1. 如果您处理的是嵌套结构,则必须实现递归函数以实现此转换。
  2. 进行转换会有一些额外开销。
代码:
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();
    }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接