英文:
How to deserialise JSON string to object relaxing root value case sensitivity?
问题
遇到将项目从Jersey
迁移到Spring MVC
时的问题。
如何在Jackson
中放宽根值的大小写敏感性?
我想支持大小写都可以。
我们有以下Jackson
配置,适用于属性和枚举,但不适用于根值:
spring.jackson.mapper.accept-case-insensitive-properties=true
spring.jackson.mapper.accept-case-insensitive-enums=true
在我的情况下,我无法访问Car
类,因此无法使用任何Jackson
注解,如@JsonRootValue
来更新Car
类的名称为'car'。
这是一个带有用于复现问题的测试的示例类。
在Jackson
库中放宽根名称是否不错?
public class CarTest {
public class Car {
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@Test
public void testCarRootValueCaseSensitivity() throws IOException {
Car car = new Car();
car.setName("audi");
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
objectMapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
String carAsString = objectMapper.writeValueAsString(car);
System.out.println(carAsString);
// Works fine with out any exception as the root value Car has captital 'C'
objectMapper.readValue("{\"Car\":{\"name\":\"audi\"}}", Car.class);
// Throws exception when lower case 'c' is provided than uppercase 'C'
objectMapper.readValue("{\"car\":{\"name\":\"audi\"}}", Car.class);
}
}
英文:
Facing issue while moving my project from Jersey
to Spring MVC
.
How to relax case insensitivity for root value in Jackson
?
I would like to support both upper and lower case.
We have below Jackson
configurations which works fine for properties and enums but not for root value
spring.jackson.mapper.accept-case-insensitive-properties=true
spring.jackson.mapper.accept-case-insensitive-enums=true
In my case, I do not have access to Car
class and hence can not use any of the Jackson
annotations like @JsonRootValue
to update the name of Car
class to 'car'
Here is the sample class with test to reproduce the issue, I am facing.
Wouldn't it be nice to have configuration to relax root name in jackson library?
public class CarTest {
public class Car {
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@Test
public void testCarRootValueCaseSensitivity() throws IOException {
Car car = new Car();
car.setName("audi");
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
objectMapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
String carAsString = objectMapper.writeValueAsString(car);
System.out.println(carAsString);
// Works fine with out any exception as the root value Car has captital 'C'
objectMapper.readValue("{\"Car\":{\"name\":\"audi\"}}", Car.class);
// Throws exception when lower case 'c' is provided than uppercase 'C'
objectMapper.readValue("{\"car\":{\"name\":\"audi\"}}", Car.class);
}
}
答案1
得分: 1
当您查看抛出的异常时,可以注意到 _unwrapAndDeserialize
方法名。
在这个方法中,您可以找到以下代码片段:
String actualName = p.getCurrentName();
if (!expSimpleName.equals(actualName)) {
ctxt.reportPropertyInputMismatch(rootType, actualName,
"Root name '%s' does not match expected ('%s') for type %s",
actualName, expSimpleName, rootType);
}
由于始终使用 equals
方法,无法配置此行为的方式。
如果您真的想以不区分大小写的方式解析它,可以覆盖 _unwrapAndDeserialize
方法,并将 equals
替换为 equalsIgnoreCase
。示例类如下:
class CaseInsensitiveObjectMapper extends ObjectMapper {
protected Object _unwrapAndDeserialize(JsonParser p, DeserializationContext ctxt, DeserializationConfig config, JavaType rootType, JsonDeserializer<Object> deser) throws IOException {
PropertyName expRootName = config.findRootName(rootType);
// 12-Jun-2015, tatu: Should try to support namespaces etc but...
String expSimpleName = expRootName.getSimpleName();
if (p.getCurrentToken() != JsonToken.START_OBJECT) {
ctxt.reportWrongTokenException(rootType, JsonToken.START_OBJECT,
"Current token not START_OBJECT (needed to unwrap root name '%s'), but %s",
expSimpleName, p.getCurrentToken());
}
if (p.nextToken() != JsonToken.FIELD_NAME) {
ctxt.reportWrongTokenException(rootType, JsonToken.FIELD_NAME,
"Current token not FIELD_NAME (to contain expected root name '%s'), but %s",
expSimpleName, p.getCurrentToken());
}
String actualName = p.getCurrentName();
if (!expSimpleName.equalsIgnoreCase(actualName)) {
ctxt.reportPropertyInputMismatch(rootType, actualName,
"Root name '%s' does not match expected ('%s') for type %s",
actualName, expSimpleName, rootType);
}
// ok, then move to value itself....
p.nextToken();
Object result = deser.deserialize(p, ctxt);
// and last, verify that we now get matching END_OBJECT
if (p.nextToken() != JsonToken.END_OBJECT) {
ctxt.reportWrongTokenException(rootType, JsonToken.END_OBJECT,
"Current token not END_OBJECT (to match wrapper object with root name '%s'), but %s",
expSimpleName, p.getCurrentToken());
}
if (config.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)) {
_verifyNoTrailingTokens(p, ctxt, rootType);
}
return result;
}
}
我从 ObjectMapper
类中复制了整个方法(版本为 2.10.1
),如果您升级 Jackson
版本,需要检查此方法的实现是否有变化,并在需要时进行替换。
最后,您可以在测试中使用这个新类型:
ObjectMapper objectMapper = new CaseInsensitiveObjectMapper();
参考链接:
英文:
When you take a look on exception which is thrown:
Exception in thread "main" com.fasterxml.jackson.databind.exc.MismatchedInputException: Root name 'car' does not match expected ('Car') for type [simple type, class com.example.Car]
at [Source: (String)"{"car":{"name":"audi"}}"; line: 1, column: 2] (through reference chain: com.example.Car["car"])
at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
at com.fasterxml.jackson.databind.DeserializationContext.reportPropertyInputMismatch(DeserializationContext.java:1477)
at com.fasterxml.jackson.databind.DeserializationContext.reportPropertyInputMismatch(DeserializationContext.java:1493)
at com.fasterxml.jackson.databind.ObjectMapper._unwrapAndDeserialize(ObjectMapper.java:4286)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4200)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3205)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3173)
you can notice _unwrapAndDeserialize
method name.
In this method you can find this piece of code:
String actualName = p.getCurrentName();
if (!expSimpleName.equals(actualName)) {
ctxt.reportPropertyInputMismatch(rootType, actualName,
"Root name '%s' does not match expected ('%s') for type %s",
actualName, expSimpleName, rootType);
}
There is no way to configure this behaviour since always equals
method is used.
If you really want to parse it with case insensitive mode you can override _unwrapAndDeserialize
method and replace equals
with equalsIgnoreCase
. Example class:
class CaseInsensitiveObjectMapper extends ObjectMapper {
protected Object _unwrapAndDeserialize(JsonParser p, DeserializationContext ctxt, DeserializationConfig config, JavaType rootType, JsonDeserializer<Object> deser) throws IOException {
PropertyName expRootName = config.findRootName(rootType);
// 12-Jun-2015, tatu: Should try to support namespaces etc but...
String expSimpleName = expRootName.getSimpleName();
if (p.getCurrentToken() != JsonToken.START_OBJECT) {
ctxt.reportWrongTokenException(rootType, JsonToken.START_OBJECT,
"Current token not START_OBJECT (needed to unwrap root name '%s'), but %s",
expSimpleName, p.getCurrentToken());
}
if (p.nextToken() != JsonToken.FIELD_NAME) {
ctxt.reportWrongTokenException(rootType, JsonToken.FIELD_NAME,
"Current token not FIELD_NAME (to contain expected root name '%s'), but %s",
expSimpleName, p.getCurrentToken());
}
String actualName = p.getCurrentName();
if (!expSimpleName.equalsIgnoreCase(actualName)) {
ctxt.reportPropertyInputMismatch(rootType, actualName,
"Root name '%s' does not match expected ('%s') for type %s",
actualName, expSimpleName, rootType);
}
// ok, then move to value itself....
p.nextToken();
Object result = deser.deserialize(p, ctxt);
// and last, verify that we now get matching END_OBJECT
if (p.nextToken() != JsonToken.END_OBJECT) {
ctxt.reportWrongTokenException(rootType, JsonToken.END_OBJECT,
"Current token not END_OBJECT (to match wrapper object with root name '%s'), but %s",
expSimpleName, p.getCurrentToken());
}
if (config.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)) {
_verifyNoTrailingTokens(p, ctxt, rootType);
}
return result;
}
}
I copied the whole method from ObjectMapper
class (version 2.10.1
) and in case you will upgrade Jackson
version you need to check how implementation of this method looks like and replace it if needed.
Finally, you can use this new type in your test:
ObjectMapper objectMapper = new CaseInsensitiveObjectMapper();
See also:
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论