使用 Jackson 反序列化时如何放宽命名策略?
How can I loosen up the naming strategy when deserializing using Jackson?
我一直在尝试升级 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);
}
}
@JsonProperty
覆盖任何 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 以在每个注释级别切换它,但这对您没有真正帮助。
似乎 Codehaus 在映射时确实在 @JsonProperty
值之上应用了 PropertyNamingStrategy
,尽管我找不到任何明确的文档。这似乎也是 2.4.0 之前的 fasterxml 中的行为。 Here 是注意到相同行为差异的另一个例子。
虽然 SkinnyJ 提供的解决方案非常适合您的问题,但如果您不能等到 2.7 发布,您可以应用以下 hack 来解决这个问题。
想法是转换传入的 JSON 以匹配 bean 定义中的属性。下面的代码就是这样做的。需要注意以下几点:
- 如果你处理的是嵌套结构,你将不得不实现一个递归函数来实现这种转换。
- 进行转换会涉及一些开销。
代码:
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();
}
}
我一直在尝试升级 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);
}
}
@JsonProperty
覆盖任何 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 以在每个注释级别切换它,但这对您没有真正帮助。
似乎 Codehaus 在映射时确实在 @JsonProperty
值之上应用了 PropertyNamingStrategy
,尽管我找不到任何明确的文档。这似乎也是 2.4.0 之前的 fasterxml 中的行为。 Here 是注意到相同行为差异的另一个例子。
虽然 SkinnyJ 提供的解决方案非常适合您的问题,但如果您不能等到 2.7 发布,您可以应用以下 hack 来解决这个问题。
想法是转换传入的 JSON 以匹配 bean 定义中的属性。下面的代码就是这样做的。需要注意以下几点:
- 如果你处理的是嵌套结构,你将不得不实现一个递归函数来实现这种转换。
- 进行转换会涉及一些开销。
代码:
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();
}
}