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

Java Jackson能否以不区分大小写的方式检查重复的属性?,java,json,jackson,deserialization,Java,Json,Jackson,Deserialization,我使用Jackson JSON将一些JSON对象转换为POJO类。此反序列化应不区分大小写,并且不允许名称重复 如下所示配置ObjectMapper,可启用不区分大小写的反序列化,并在具有完全相同名称的属性时失败: final ObjectMapper ObjectMapper; configure(MapperFeature.ACCEPT\u不区分大小写的\u属性,true); enable(JsonParser.Feature.STRICT\u DUPLICATE\u DETECTION);

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

如下所示配置
ObjectMapper
,可启用不区分大小写的反序列化,并在具有完全相同名称的属性时失败:

final ObjectMapper ObjectMapper;
configure(MapperFeature.ACCEPT\u不区分大小写的\u属性,true);
enable(JsonParser.Feature.STRICT\u DUPLICATE\u DETECTION);
但是,当输入包含两个具有相同名称和不同大小写的属性时,它不会失败,例如:

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

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

来自
严格重复检测
文档:

确定JsonParser是否会显式检查 未遇到重复的JSON对象字段名称。如果启用, 解析器将检查上下文中的所有名称,并通过 抛出JsonParseException;如果禁用,解析器将不会执行此操作 检查后一种情况下的假设是调用方负责 在更高级别处理重复项:例如,数据绑定具有 用于指定要在此处执行的检测的功能。请注意,启用 由于必须存储,此功能将产生性能开销 并检查其他信息:这通常会增加20-30%的成本 基本解析的执行时间

默认情况下,
JSON
是区分大小写的,这是在
Jackson
中默认不启用不区分大小写的主要原因之一。但我们可以扩展基本实现并添加验证。我们需要扩展
com.fasterxml.jackson.databind.deser.beandSerializerModifier
com.fasterxml.jackson.databind.deser.beandSerializer
,它们反序列化
POJO
类。下面的解决方案取决于您正在使用的版本,因为我从基类复制了一些代码,这些代码还没有准备好拦截额外的功能。如果您的
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);
        }
    }
}
对于上述
JSON
有效载荷打印:

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

非常感谢你的详细回答,你真是太好了!我将立即尝试您的建议,并让您知道它是如何运行的。它确实起了作用,唯一的问题是建议的解决方案与其他配置不兼容,例如
接受\u CASE\u INSENSITIVE\u属性
,但我仍然认为我可以找出一些问题。再次感谢您抽出时间提供如此完整的答案@塞尔楚,听到这个消息我很高兴。我用
ACCEPT\u CASE\u INSENSITIVE\u属性测试了这个解决方案,它对我来说没有任何问题。示例
insensitiveBeandSerializer
使用来自
BeandSerializer
的实现,该实现来自
Jackson
,并依赖于
Jackson
的版本。我的示例适用于最新版本。如果使用不同的版本,则需要从当前版本复制代码。您是否可以尝试创建简单的应用程序,并将上面的示例用于
2.9.8
版本?
Exception in thread "main" InsensitiveBeanDeserializer$DuplicateInsensitiveKeysException: Properties 'NAME' and 'Name' are the same!