All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.fasterxml.jackson.databind.jsontype.impl.AsDeductionTypeDeserializer Maven / Gradle / Ivy

There is a newer version: 2.17.0
Show newest version
package com.fasterxml.jackson.databind.jsontype.impl;

import java.io.IOException;
import java.util.*;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;

import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.TokenBuffer;

/**
 * A {@link TypeDeserializer} capable of deducing polymorphic types based on the
 * fields available. Deduction is limited to the names of child properties
 * (not their values or, consequently, any nested descendants).
 * Exceptions will be thrown if not enough unique information is present
 * to select a single subtype.
 * 

* The current deduction process does not support pojo-hierarchies such that * the absence of child fields infers a parent type. That is, every deducible subtype * MUST have some unique fields and the input data MUST contain said unique fields * to provide a positive match. * * @since 2.12 */ public class AsDeductionTypeDeserializer extends AsPropertyTypeDeserializer { private static final long serialVersionUID = 1L; // 03-May-2021, tatu: for [databind#3139], support for "empty" type private static final BitSet EMPTY_CLASS_FINGERPRINT = new BitSet(0); // Fieldname -> bitmap-index of every field discovered, across all subtypes private final Map fieldBitIndex; // Bitmap of available fields in each subtype (including its parents) private final Map subtypeFingerprints; public AsDeductionTypeDeserializer(JavaType bt, TypeIdResolver idRes, JavaType defaultImpl, DeserializationConfig config, Collection subtypes) { super(bt, idRes, null, false, defaultImpl, null); fieldBitIndex = new HashMap<>(); subtypeFingerprints = buildFingerprints(config, subtypes); } public AsDeductionTypeDeserializer(AsDeductionTypeDeserializer src, BeanProperty property) { super(src, property); fieldBitIndex = src.fieldBitIndex; subtypeFingerprints = src.subtypeFingerprints; } @Override public TypeDeserializer forProperty(BeanProperty prop) { return (prop == _property) ? this : new AsDeductionTypeDeserializer(this, prop); } protected Map buildFingerprints(DeserializationConfig config, Collection subtypes) { boolean ignoreCase = config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); int nextField = 0; Map fingerprints = new HashMap<>(); for (NamedType subtype : subtypes) { JavaType subtyped = config.getTypeFactory().constructType(subtype.getType()); List properties = config.introspect(subtyped).findProperties(); BitSet fingerprint = new BitSet(nextField + properties.size()); for (BeanPropertyDefinition property : properties) { String name = property.getName(); if (ignoreCase) name = name.toLowerCase(); Integer bitIndex = fieldBitIndex.get(name); if (bitIndex == null) { bitIndex = nextField; fieldBitIndex.put(name, nextField++); } fingerprint.set(bitIndex); } String existingFingerprint = fingerprints.put(fingerprint, subtype.getType().getName()); // Validate uniqueness if (existingFingerprint != null) { throw new IllegalStateException( String.format("Subtypes %s and %s have the same signature and cannot be uniquely deduced.", existingFingerprint, subtype.getType().getName()) ); } } return fingerprints; } @Override public Object deserializeTypedFromObject(JsonParser p, DeserializationContext ctxt) throws IOException { JsonToken t = p.currentToken(); if (t == JsonToken.START_OBJECT) { t = p.nextToken(); } else if (/*t == JsonToken.START_ARRAY ||*/ t != JsonToken.FIELD_NAME) { /* This is most likely due to the fact that not all Java types are * serialized as JSON Objects; so if "as-property" inclusion is requested, * serialization of things like Lists must be instead handled as if * "as-wrapper-array" was requested. * But this can also be due to some custom handling: so, if "defaultImpl" * is defined, it will be asked to handle this case. */ return _deserializeTypedUsingDefaultImpl(p, ctxt, null, "Unexpected input"); } // 03-May-2021, tatu: [databind#3139] Special case, "empty" Object if (t == JsonToken.END_OBJECT) { String emptySubtype = subtypeFingerprints.get(EMPTY_CLASS_FINGERPRINT); if (emptySubtype != null) { // ... and an "empty" subtype registered return _deserializeTypedForId(p, ctxt, null, emptySubtype); } } List candidates = new LinkedList<>(subtypeFingerprints.keySet()); // Keep track of processed tokens as we must rewind once after deducing // the deserializer to use @SuppressWarnings("resource") final TokenBuffer tb = ctxt.bufferForInputBuffering(p); boolean ignoreCase = ctxt.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); for (; t == JsonToken.FIELD_NAME; t = p.nextToken()) { String name = p.currentName(); if (ignoreCase) name = name.toLowerCase(); tb.copyCurrentStructure(p); Integer bit = fieldBitIndex.get(name); if (bit != null) { // field is known by at least one subtype prune(candidates, bit); if (candidates.size() == 1) { return _deserializeTypedForId(p, ctxt, tb, subtypeFingerprints.get(candidates.get(0))); } } } // We have zero or multiple candidates, deduction has failed String msgToReportIfDefaultImplFailsToo = String.format("Cannot deduce unique subtype of %s (%d candidates match)", ClassUtil.getTypeDescription(_baseType), candidates.size()); return _deserializeTypedUsingDefaultImpl(p, ctxt, tb, msgToReportIfDefaultImplFailsToo); } // Keep only fingerprints containing this field private static void prune(List candidates, int bit) { for (Iterator iter = candidates.iterator(); iter.hasNext(); ) { if (!iter.next().get(bit)) { iter.remove(); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy