PairDeserializer.java
package top.infra.jackson2.deser;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
/**
* see: {@link com.fasterxml.jackson.databind.deser.std.MapEntryDeserializer}.
*/
public class PairDeserializer extends StdDeserializer<Pair<Object, Object>> implements ContextualDeserializer {
private static final long serialVersionUID = 1;
// // Configuration: typing, deserializers
protected final JavaType _type;
/**
* Key deserializer to use; either passed via constructor
* (when indicated by annotations), or resolved when
* {@link #createContextual} is called;
*/
protected final JsonDeserializer<Object> _keyDeserializer;
/**
* If key instances have polymorphic type information, this
* is the type deserializer that can handle it
*/
protected final TypeDeserializer _keyTypeDeserializer;
/**
* Value deserializer.
*/
protected final JsonDeserializer<Object> _valueDeserializer;
/**
* If value instances have polymorphic type information, this
* is the type deserializer that can handle it
*/
protected final TypeDeserializer _valueTypeDeserializer;
/*
/**********************************************************
/* Life-cycle
/**********************************************************
*/
public PairDeserializer(
final JavaType type,
final JsonDeserializer<Object> keyDeser,
final TypeDeserializer keyTypeDeserializer,
final JsonDeserializer<Object> valueDeser,
final TypeDeserializer valueTypeDeser
) {
super(type);
if (type.containedTypeCount() != 2) { // sanity check
throw new IllegalArgumentException("Missing generic type information for " + type);
}
this._type = type;
this._keyDeserializer = keyDeser;
this._keyTypeDeserializer = keyTypeDeserializer;
this._valueDeserializer = valueDeser;
this._valueTypeDeserializer = valueTypeDeser;
}
/**
* Copy-constructor that can be used by sub-classes to allow
* copy-on-write styling copying of settings of an existing instance.
*/
protected PairDeserializer(final PairDeserializer src) {
super(src._type);
this._type = src._type;
this._keyDeserializer = src._keyDeserializer;
this._keyTypeDeserializer = src._keyTypeDeserializer;
this._valueDeserializer = src._valueDeserializer;
this._valueTypeDeserializer = src._valueTypeDeserializer;
}
protected PairDeserializer(
final PairDeserializer src,
final JsonDeserializer<Object> keyDeser,
final TypeDeserializer keyTypeDeserializer,
final JsonDeserializer<Object> valueDeser,
final TypeDeserializer valueTypeDeser
) {
super(src._type);
this._type = src._type;
this._keyDeserializer = keyDeser;
this._keyTypeDeserializer = keyTypeDeserializer;
this._valueDeserializer = valueDeser;
this._valueTypeDeserializer = valueTypeDeser;
}
/**
* Fluent factory method used to create a copy with slightly
* different settings. When sub-classing, MUST be overridden.
*/
@SuppressWarnings("unchecked")
protected PairDeserializer withResolved(
final TypeDeserializer keyTypeDeserializer, final JsonDeserializer<?> keyDeser,
final TypeDeserializer valueTypeDeser, final JsonDeserializer<?> valueDeser
) {
if ((this._keyDeserializer == keyDeser) && (this._valueDeserializer == valueDeser)
&& (this._valueTypeDeserializer == valueTypeDeser)) {
return this;
}
return new PairDeserializer(
this,
(JsonDeserializer<Object>) keyDeser,
keyTypeDeserializer,
(JsonDeserializer<Object>) valueDeser,
valueTypeDeser);
}
/*
/**********************************************************
/* Validation, post-processing (ResolvableDeserializer)
/**********************************************************
*/
/**
* Method called to finalize setup of this deserializer,
* when it is known for which property deserializer is needed for.
*/
@Override
public JsonDeserializer<?> createContextual(
final DeserializationContext ctxt,
final BeanProperty property
) throws JsonMappingException {
JsonDeserializer<?> kd = this._keyDeserializer;
kd = findConvertingContentDeserializer(ctxt, property, kd);
JavaType keyType = this._type.containedType(0);
if (kd == null) {
kd = ctxt.findContextualValueDeserializer(keyType, property);
} else { // if directly assigned, probably not yet contextual, so:
kd = ctxt.handleSecondaryContextualization(kd, property, keyType);
}
TypeDeserializer ktd = this._keyTypeDeserializer;
if (ktd != null) {
ktd = ktd.forProperty(property);
}
JsonDeserializer<?> vd = this._valueDeserializer;
vd = findConvertingContentDeserializer(ctxt, property, vd);
JavaType valueType = this._type.containedType(1);
if (vd == null) {
vd = ctxt.findContextualValueDeserializer(valueType, property);
} else { // if directly assigned, probably not yet contextual, so:
vd = ctxt.handleSecondaryContextualization(vd, property, valueType);
}
TypeDeserializer vtd = this._valueTypeDeserializer;
if (vtd != null) {
vtd = vtd.forProperty(property);
}
return withResolved(ktd, kd, vtd, vd);
}
/*
/**********************************************************
/* JsonDeserializer API
/**********************************************************
*/
@SuppressWarnings("unchecked")
@Override
public Pair<Object, Object> deserialize(final JsonParser p, final DeserializationContext ctxt) throws IOException {
// Ok: must point to START_OBJECT, FIELD_NAME or END_OBJECT
JsonToken t = p.getCurrentToken();
if (t != JsonToken.START_OBJECT && t != JsonToken.FIELD_NAME && t != JsonToken.END_OBJECT) {
// String may be ok however:
// slightly redundant (since String was passed above), but
return _deserializeFromEmpty(p, ctxt);
}
if (t == JsonToken.START_OBJECT) {
t = p.nextToken();
}
if (t != JsonToken.FIELD_NAME) {
if (t == JsonToken.END_OBJECT) {
ctxt.reportMappingException("Can not deserialize a Pair out of empty JSON Object");
return null;
}
return (Pair<Object, Object>) ctxt.handleUnexpectedToken(handledType(), p);
}
Object key = null;
Object value = null;
Pair<String, Object> field = this.deserializeField(p, ctxt);
if ("left".equals(field.getKey())) {
key = field.getRight();
} else {
value = field.getRight();
}
t = p.nextToken();
field = this.deserializeField(p, ctxt);
if ("left".equals(field.getKey())) {
key = field.getRight();
} else {
value = field.getRight();
}
// Close, but also verify that we reached the END_OBJECT
t = p.nextToken();
if (t != JsonToken.END_OBJECT) {
if (t == JsonToken.FIELD_NAME) { // most likely
ctxt.reportMappingException("Problem binding JSON into Pair: more than one entry in JSON (second field: '" + p.getCurrentName() + "')");
} else {
// how would this occur?
ctxt.reportMappingException("Problem binding JSON into Pair: unexpected content after JSON Object entry: " + t);
}
return null;
}
return new ImmutablePair<>(key, value);
}
private Pair<String, Object> deserializeField(
final JsonParser p,
final DeserializationContext ctxt
) throws IOException {
final String fieldName = p.getCurrentName();
Object fieldValue = null;
if ("left".equals(fieldName)) {
final JsonDeserializer<Object> keyDes = this._keyDeserializer;
final TypeDeserializer keyTypeDeser = this._keyTypeDeserializer;
fieldValue = this.deserializeField(p, ctxt, fieldName, keyDes, keyTypeDeser);
} else if ("right".equals(fieldName)) {
final JsonDeserializer<Object> valueDes = this._valueDeserializer;
final TypeDeserializer valueTypeDeser = this._valueTypeDeserializer;
fieldValue = this.deserializeField(p, ctxt, fieldName, valueDes, valueTypeDeser);
} else {
ctxt.reportMappingException("Problem binding JSON into Pair: unknown field " + fieldName + ".");
}
return new ImmutablePair<>(fieldName, fieldValue);
}
private Object deserializeField(
final JsonParser p,
final DeserializationContext ctxt,
final String fieldName,
final JsonDeserializer<Object> des,
final TypeDeserializer typeDeser
) throws IOException {
Object fieldValue = null;
JsonToken t = p.nextToken();
try {
// Note: must handle null explicitly here; value deserializers won't
if (t == JsonToken.VALUE_NULL) {
fieldValue = des.getNullValue(ctxt);
} else if (typeDeser == null) {
fieldValue = des.deserialize(p, ctxt);
} else {
fieldValue = des.deserializeWithType(p, ctxt, typeDeser);
}
} catch (final Exception ex) {
wrapAndThrow(ex, Pair.class, fieldName);
}
return fieldValue;
}
@Override
public Pair<Object, Object> deserialize(
final JsonParser p, final DeserializationContext ctxt, final Pair<Object, Object> result
) throws IOException {
throw new IllegalStateException("Can not update Pair values");
}
@Override
public Object deserializeWithType(
final JsonParser p,
final DeserializationContext ctxt,
final TypeDeserializer typeDeserializer
) throws IOException, JsonProcessingException {
// In future could check current token... for now this should be enough:
return typeDeserializer.deserializeTypedFromObject(p, ctxt);
}
/*
/**********************************************************
/* Other public accessors
/**********************************************************
*/
@Override
public JavaType getValueType() {
return this._type;
}
/*
/**********************************************************
/* Shared methods for sub-classes (from ContainerDeserializerBase)
/**********************************************************
*/
/**
* Helper method called by various Map(-like) deserializers.
*/
protected void wrapAndThrow(Throwable t, final Object ref, String key) throws IOException {
// to handle StackOverflow:
while (t instanceof InvocationTargetException && t.getCause() != null) {
t = t.getCause();
}
// Errors and "plain" IOExceptions to be passed as is
if (t instanceof Error) {
throw (Error) t;
}
// ... except for mapping exceptions
if (t instanceof IOException && !(t instanceof JsonMappingException)) {
throw (IOException) t;
}
// for [databind#1141]
if (key == null) {
key = "N/A";
}
throw JsonMappingException.wrapWithPath(t, ref, key);
}
}