From STRICT_DUPLICATE_DETECTION
documentation:
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
by default is case sensitive and this one of main reason why making it insensitive is not enabled by default in Jackson
. But we can extend basic implementation and add validation. We need to extend com.fasterxml.jackson.databind.deser.BeanDeserializerModifier
and com.fasterxml.jackson.databind.deser.BeanDeserializer
which deserialises POJO
classes. Below solution depends from version you are using because I copied some code from base class which is not ready for intercepting extra functionality. If you do not have any extra configuration for your POJO
classes vanillaDeserialize
method will be invoked and this one we will try to improve. Let's implement it:
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);
}
}
}
Example usage:
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));
}
}
For above JSON
payloads prints:
Exception in thread "main" InsensitiveBeanDeserializer$DuplicateInsensitiveKeysException: Properties 'NAME' and 'Name' are the same!