【问题标题】:Can Jackson check for duplicated properties in a case insensitive way?杰克逊可以以不区分大小写的方式检查重复的属性吗?
【发布时间】:2019-08-17 22:54:47
【问题描述】:

我正在使用 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 失败?

【问题讨论】:

    标签: java json jackson deserialization


    【解决方案1】:

    来自STRICT_DUPLICATE_DETECTION 文档:

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

    JSON 默认情况下区分大小写,这是Jackson 默认情况下不启用不区分大小写的主要原因之一。但是我们可以扩展基本实现并添加验证。我们需要扩展 com.fasterxml.jackson.databind.deser.BeanDeserializerModifiercom.fasterxml.jackson.databind.deser.BeanDeserializer 以反序列化 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);
            }
        }
    }
    

    示例用法:

    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!
    

    【讨论】:

    • 非常感谢您的详细回答,您真是太好了!我会立即尝试你的建议,并告诉你进展如何。
    • 它确实有效,唯一的问题是建议的解决方案与其他配置不兼容,例如ACCEPT_CASE_INSENSITIVE_PROPERTIES,但我仍然认为我可以弄清楚一些事情。再次感谢您抽出宝贵时间提供如此完整的答案!
    • @Serchu,我很高兴听到这个消息。我用ACCEPT_CASE_INSENSITIVE_PROPERTIES 测试了这个解决方案,它对我有用,没有任何问题。示例InsensitiveBeanDeserializer 使用来自BeanDeserializer 的实现,它来自Jackson 并依赖于Jackson 的版本。我的示例适用于最新版本 2.9.8。如果您使用不同的版本,您需要从当前版本复制代码。您可以尝试创建简单的应用程序并使用上面的示例和2.9.8 版本吗?
    猜你喜欢
    • 1970-01-01
    • 2014-08-01
    • 1970-01-01
    • 2014-01-22
    • 2017-04-20
    • 1970-01-01
    • 2019-10-16
    • 1970-01-01
    相关资源
    最近更新 更多