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

时间:2023-01-28 18:02:32

I've been trying to upgrade the JSON modules to use the FasterXML (2.6.3) versions of Jackson instead of the old Codehaus modules. During the upgrade, I've noticed that the naming strategy differs when using FasterXML instead of Codehaus.

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

Codehaus was more flexible when it came to the naming strategy. The test below highlights the issue I'm facing with FasterXML. How can I configure the ObjectMapper so it follows the same strategy like Codehaus?

Codehaus在命名策略上更灵活。下面的测试强调了我所面对的FasterXML的问题。如何配置ObjectMapper,使其遵循类似Codehaus的策略?

I cannot alter the JSONProperty annotations as there are hundreds of them. I would like the upgrade to be backwards compatible with respect to the naming strategy.

我不能修改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 个解决方案

#1


8  

@JsonProperty overrides any PropertyNamingStrategy in fasterxml since version 2.4.0. However, yet-to-be-released version 2.7.0 will provide a feature to allow you to opt back in to the old behavior. There is also an unimplemented suggestion to toggle this at the per-annotation level, but that would not really help you.

@JsonProperty自2.4.0版本以来重写了fasterxml中的任何PropertyNamingStrategy。然而,尚未发布的2.7.0版本将提供一个特性,允许您选择回到旧的行为。还有一个未实现的建议,可以在每个注释级别上进行切换,但这并不能真正帮助您。

It appears that Codehaus does apply the PropertyNamingStrategy on top of the @JsonProperty values when mapping, although I can't find any clear docs on that. This appears to have been the behavior in fasterxml before 2.4.0 as well. Here is another example of someone noticing the same difference in behavior.

在映射时,Codehaus的确将PropertyNamingStrategy应用于@JsonProperty值之上,尽管我找不到任何明确的文档。这似乎也是fasterxml在2.4.0之前的行为。这是另一个注意到行为差异的人的例子。

#2


1  

Although the solution provided by SkinnyJ is perfect for your problem, but if you can't wait till 2.7 is released, you can apply the below hack to get around the problem.

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

The idea is to transform the incoming JSON to match the attributes in your bean definition. Below code does that. Following points should be noted:

其思想是转换传入的JSON以匹配bean定义中的属性。下面的代码。应注意以下几点:

  1. If you are dealing with nested structures, you will have to implement a recursive function to achieve this transformation.
  2. 如果要处理嵌套结构,则必须实现一个递归函数来实现这种转换。
  3. There is a little overhead involved in doing the transformation.
  4. 在进行转换时,会有一些开销。

Code:

代码:

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

}

#1


8  

@JsonProperty overrides any PropertyNamingStrategy in fasterxml since version 2.4.0. However, yet-to-be-released version 2.7.0 will provide a feature to allow you to opt back in to the old behavior. There is also an unimplemented suggestion to toggle this at the per-annotation level, but that would not really help you.

@JsonProperty自2.4.0版本以来重写了fasterxml中的任何PropertyNamingStrategy。然而,尚未发布的2.7.0版本将提供一个特性,允许您选择回到旧的行为。还有一个未实现的建议,可以在每个注释级别上进行切换,但这并不能真正帮助您。

It appears that Codehaus does apply the PropertyNamingStrategy on top of the @JsonProperty values when mapping, although I can't find any clear docs on that. This appears to have been the behavior in fasterxml before 2.4.0 as well. Here is another example of someone noticing the same difference in behavior.

在映射时,Codehaus的确将PropertyNamingStrategy应用于@JsonProperty值之上,尽管我找不到任何明确的文档。这似乎也是fasterxml在2.4.0之前的行为。这是另一个注意到行为差异的人的例子。

#2


1  

Although the solution provided by SkinnyJ is perfect for your problem, but if you can't wait till 2.7 is released, you can apply the below hack to get around the problem.

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

The idea is to transform the incoming JSON to match the attributes in your bean definition. Below code does that. Following points should be noted:

其思想是转换传入的JSON以匹配bean定义中的属性。下面的代码。应注意以下几点:

  1. If you are dealing with nested structures, you will have to implement a recursive function to achieve this transformation.
  2. 如果要处理嵌套结构,则必须实现一个递归函数来实现这种转换。
  3. There is a little overhead involved in doing the transformation.
  4. 在进行转换时,会有一些开销。

Code:

代码:

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

}