java - Jackson 能否以不区分大小写的方式检查重复的属性?

标签 java json jackson deserialization

我正在使用 Jackson JSON 将一些 JSON 对象转换为 POJO 类。此反序列化应该不区分大小写,并且不应允许具有不区分大小写重复名称的属性。

如下所示配置ObjectMapper可以启用不区分大小写的反序列化,并且对具有严格相同名称的属性进行失败:

final ObjectMapper objectMapper;
objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
objectMapper.enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION);

但是,当输入包含两个具有相同名称和不同大小写的属性时,它不会失败,如下所示:

{
   "Name": "name01",
   "NAME": "name02"
}

有没有办法配置ObjectMapper在这种情况下失败?

最佳答案

来自 STRICT_DUPLICATE_DETECTION 文档:

Feature that determines whether JsonParser will explicitly check that no duplicate JSON Object field names are encountered. If enabled, parser will check all names within context and report duplicates by throwing a JsonParseException; if disabled, parser will not do such checking. Assumption in latter case is that caller takes care of handling duplicates at a higher level: data-binding, for example, has features to specify detection to be done there. Note that enabling this feature will incur performance overhead due to having to store and check additional information: this typically adds 20-30% to execution time for basic parsing.

JSON 默认情况下区分大小写,这也是 Jackson 中默认不启用使其不敏感的主要原因之一。但我们可以扩展基本实现并添加验证。我们需要扩展 com.fasterxml.jackson.databind.deser.BeanDeserializerModifier 和 com.fasterxml.jackson.databind.deser.BeanDeserializer 来反序列化 POJO > 类(class)。下面的解决方案取决于您使用的版本,因为我从基类复制了一些代码,该代码尚未准备好拦截额外的功能。如果您的 POJO 类没有任何额外的配置,则将调用 vanillaDeserialize 方法,我们将尝试改进该方法。让我们实现它:

class InsensitiveBeanDeserializerModifier extends BeanDeserializerModifier {

    @Override
    public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
        JsonDeserializer<?> base = super.modifyDeserializer(config, beanDesc, deserializer);
        if (base instanceof BeanDeserializer) {
            return new InsensitiveBeanDeserializer((BeanDeserializer) base);
        }

        return base;
    }
}

class InsensitiveBeanDeserializer extends BeanDeserializer {

    public InsensitiveBeanDeserializer(BeanDeserializerBase src) {
        super(src);
    }

    public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        // common case first
        if (p.isExpectedStartObjectToken()) {
            if (_vanillaProcessing) {
                return vanillaDeserialize(p, ctxt, p.nextToken());
            }
            // 23-Sep-2015, tatu: This is wrong at some many levels, but for now... it is
            //    what it is, including "expected behavior".
            p.nextToken();
            if (_objectIdReader != null) {
                return deserializeWithObjectId(p, ctxt);
            }
            return deserializeFromObject(p, ctxt);
        }
        return _deserializeOther(p, ctxt, p.getCurrentToken());
    }

    protected Object vanillaDeserialize(JsonParser p, DeserializationContext ctxt, JsonToken t) throws IOException {
        final Object bean = _valueInstantiator.createUsingDefault(ctxt);
        // [databind#631]: Assign current value, to be accessible by custom serializers
        p.setCurrentValue(bean);
        Map<String, String> names = new HashMap<>();
        if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
            String propName = p.getCurrentName();
            do {
                String oldName = names.put(propName.toLowerCase(), propName);
                if (oldName != null) {
                    String msg = "Properties '" + propName + "' and '" + oldName + "' are the same!";
                    throw new DuplicateInsensitiveKeysException(p, msg);
                }

                defaultImplementation(p, ctxt, bean, propName);
            } while ((propName = p.nextFieldName()) != null);
        }
        return bean;
    }

    private void defaultImplementation(JsonParser p, DeserializationContext ctxt, Object bean, String propName) throws IOException {
        p.nextToken();
        SettableBeanProperty prop = _beanProperties.find(propName);

        if (prop != null) { // normal case
            try {
                prop.deserializeAndSet(p, ctxt, bean);
            } catch (Exception e) {
                wrapAndThrow(e, bean, propName, ctxt);
            }
            return;
        }
        handleUnknownVanilla(p, ctxt, bean, propName);
    }

    public static class DuplicateInsensitiveKeysException extends JsonMappingException {

        public DuplicateInsensitiveKeysException(Closeable processor, String msg) {
            super(processor, msg);
        }
    }
}

使用示例:

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.JsonTokenId;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.BeanDeserializer;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBase;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.module.SimpleModule;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class JsonApp {

    public static void main(String[] args) throws Exception {
        File jsonFile = new File("./resource/test.json").getAbsoluteFile();

        SimpleModule module = new SimpleModule();
        module.setDeserializerModifier(new InsensitiveBeanDeserializerModifier());

        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(module);

        mapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);

        System.out.println(mapper.readValue(jsonFile, User.class));
    }
}

对于上述 JSON 有效负载打印:

Exception in thread "main" InsensitiveBeanDeserializer$DuplicateInsensitiveKeysException: Properties 'NAME' and 'Name' are the same!

关于java - Jackson 能否以不区分大小写的方式检查重复的属性?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55376094/

相关文章:

java - java中的递归调用导致不正确的行为

Java Arraylist 数据提取

javascript - 带有 json 正文的 POST 请求 javascript

用于反序列化具有可变小数秒数的日期的 Java 日期模式?

java - 无法以 POJO 类作为值反序列化 Map

Java套接字程序卡在sc.nextInt()处;

基于 Java 的 i18n/L10n

Java:如何编写指定父类(super class)和接口(interface)的强制转换?

php - JSON数组到PHP,按索引循环

jackson - 休眠验证器和 jackson : Using the @JsonProperty value as the ConstraintViolation PropertyPath?