如何将JSON字符串反序列化为对象并放宽根值的大小写敏感性?

huangapple go评论80阅读模式
英文:

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 &quot;main&quot; com.fasterxml.jackson.databind.exc.MismatchedInputException: Root name &#39;car&#39; does not match expected (&#39;Car&#39;) for type [simple type, class com.example.Car]
at [Source: (String)&quot;{&quot;car&quot;:{&quot;name&quot;:&quot;audi&quot;}}&quot;; line: 1, column: 2] (through reference chain: com.example.Car[&quot;car&quot;])
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,
&quot;Root name &#39;%s&#39; does not match expected (&#39;%s&#39;) for type %s&quot;,
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&lt;Object&gt; 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,
&quot;Current token not START_OBJECT (needed to unwrap root name &#39;%s&#39;), but %s&quot;,
expSimpleName, p.getCurrentToken());
}
if (p.nextToken() != JsonToken.FIELD_NAME) {
ctxt.reportWrongTokenException(rootType, JsonToken.FIELD_NAME,
&quot;Current token not FIELD_NAME (to contain expected root name &#39;%s&#39;), but %s&quot;,
expSimpleName, p.getCurrentToken());
}
String actualName = p.getCurrentName();
if (!expSimpleName.equalsIgnoreCase(actualName)) {
ctxt.reportPropertyInputMismatch(rootType, actualName,
&quot;Root name &#39;%s&#39; does not match expected (&#39;%s&#39;) for type %s&quot;,
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,
&quot;Current token not END_OBJECT (to match wrapper object with root name &#39;%s&#39;), but %s&quot;,
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:

huangapple
  • 本文由 发表于 2020年7月30日 04:25:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/63161908.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定