Skip to content

Commit

Permalink
Update release notes wrt #43, minor cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed Aug 22, 2020
1 parent 9a271ef commit 0a1ba4b
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 138 deletions.
5 changes: 5 additions & 0 deletions release-notes/CREDITS-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -1147,6 +1147,11 @@ Daniel Hrabovcak (TheSpiritXIII@github)
* Reported #2796: `TypeFactory.constructType()` does not take `TypeBindings` correctly
(2.11.2)
Mark Carter (drekbour@github)
* Contributed #43 implementation: Add option to resolve type from multiple existing properties,
`@JsonTypeInfo(use=DEDUCTION)`
(2.12.0)
Mike Gilbode (gilbode@github)
* Reported #792: Deserialization Not Working Right with Generic Types and Builders
(2.12.0)
Expand Down
3 changes: 3 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ Project: jackson-databind

2.12.0 (not yet released)

#43: Add option to resolve type from multiple existing properties,
`@JsonTypeInfo(use=DEDUCTION)`
(contributed by drekbour@github)
#426: `@JsonIgnoreProperties` does not prevent Exception Conflicting getter/setter
definitions for property
(reported by gmkll@github)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,152 +1,146 @@
package com.fasterxml.jackson.databind.jsontype.impl;

import java.io.IOException;
import java.util.BitSet;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.*;

import com.fasterxml.jackson.annotation.JsonTypeInfo;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.MapperFeature;

import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
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 <i>names</i> of child fields (not their values or, consequently, any nested descendants).
* Exceptions will be thrown if not enough unique information is present to select a single subtype.
*/
public class AsDeductionTypeDeserializer extends AsPropertyTypeDeserializer {

// Fieldname -> bitmap-index of every field discovered, across all subtypes
private final Map<String, Integer> fieldBitIndex;
// Bitmap of available fields in each subtype (including its parents)
private final Map<BitSet, String> subtypeFingerprints;

public AsDeductionTypeDeserializer(JavaType bt, TypeIdResolver idRes, JavaType defaultImpl, DeserializationConfig config, Collection<NamedType> subtypes) {
super(bt, idRes, null, false, defaultImpl);
fieldBitIndex = new HashMap<>();
subtypeFingerprints = buildFingerprints(config, subtypes);
}

public AsDeductionTypeDeserializer(AsDeductionTypeDeserializer src, BeanProperty property) {
super(src, property);
fieldBitIndex = src.fieldBitIndex;
subtypeFingerprints = src.subtypeFingerprints;
}

@Override
public JsonTypeInfo.As getTypeInclusion() {
return null;
}

@Override
public TypeDeserializer forProperty(BeanProperty prop) {
return (prop == _property) ? this : new AsDeductionTypeDeserializer(this, prop);
}

protected Map<BitSet, String> buildFingerprints(DeserializationConfig config, Collection<NamedType> subtypes) {
boolean ignoreCase = config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);

int nextField = 0;
Map<BitSet, String> fingerprints = new HashMap<>();

for (NamedType subtype : subtypes) {
JavaType subtyped = config.getTypeFactory().constructType(subtype.getType());
List<BeanPropertyDefinition> 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);
}
public class AsDeductionTypeDeserializer extends AsPropertyTypeDeserializer
{
private static final long serialVersionUID = 1L;

// Fieldname -> bitmap-index of every field discovered, across all subtypes
private final Map<String, Integer> fieldBitIndex;
// Bitmap of available fields in each subtype (including its parents)
private final Map<BitSet, String> subtypeFingerprints;

public AsDeductionTypeDeserializer(JavaType bt, TypeIdResolver idRes, JavaType defaultImpl, DeserializationConfig config, Collection<NamedType> subtypes) {
super(bt, idRes, null, false, defaultImpl);
fieldBitIndex = new HashMap<>();
subtypeFingerprints = buildFingerprints(config, subtypes);
}

String existingFingerprint = fingerprints.put(fingerprint, subtype.getType().getName());
public AsDeductionTypeDeserializer(AsDeductionTypeDeserializer src, BeanProperty property) {
super(src, property);
fieldBitIndex = src.fieldBitIndex;
subtypeFingerprints = src.subtypeFingerprints;
}

// 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())
);
}
@Override
public JsonTypeInfo.As getTypeInclusion() {
return null;
}

@Override
public TypeDeserializer forProperty(BeanProperty prop) {
return (prop == _property) ? this : new AsDeductionTypeDeserializer(this, prop);
}
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 {
/* 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);

protected Map<BitSet, String> buildFingerprints(DeserializationConfig config, Collection<NamedType> subtypes) {
boolean ignoreCase = config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);

int nextField = 0;
Map<BitSet, String> fingerprints = new HashMap<>();

for (NamedType subtype : subtypes) {
JavaType subtyped = config.getTypeFactory().constructType(subtype.getType());
List<BeanPropertyDefinition> 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;
}

List<BitSet> candidates = new LinkedList<>(subtypeFingerprints.keySet());
@Override
public Object deserializeTypedFromObject(JsonParser p, DeserializationContext ctxt) throws IOException {

JsonToken t = p.currentToken();
if (t == JsonToken.START_OBJECT) {
t = p.nextToken();
} else {
/* 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);
}

List<BitSet> candidates = new LinkedList<>(subtypeFingerprints.keySet());

// Record processed tokens as we must rewind once after deducing the deserializer to use
TokenBuffer tb = new TokenBuffer(p, ctxt);
boolean ignoreCase = ctxt.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
// Record processed tokens as we must rewind once after deducing the deserializer to use
TokenBuffer tb = new TokenBuffer(p, ctxt);
boolean ignoreCase = ctxt.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);

for (; t == JsonToken.FIELD_NAME; t = p.nextToken()) {
String name = p.getCurrentName();
if (ignoreCase) name = name.toLowerCase();
for (; t == JsonToken.FIELD_NAME; t = p.nextToken()) {
String name = p.currentName();
if (ignoreCase) name = name.toLowerCase();

tb.copyCurrentStructure(p);
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)));
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)));
}
}
}
}
}

throw new InvalidTypeIdException(
p,
String.format("Cannot deduce unique subtype of %s (%d candidates match)", _baseType.toString(), candidates.size()),
_baseType
, "DEDUCED"
);
}

// Keep only fingerprints containing this field
private static void prune(List<BitSet> candidates, int bit) {
for (Iterator<BitSet> iter = candidates.iterator(); iter.hasNext(); ) {
if (!iter.next().get(bit)) {
iter.remove();
}
throw new InvalidTypeIdException(p,
String.format("Cannot deduce unique subtype of %s (%d candidates match)",
ClassUtil.getTypeDescription(_baseType),
candidates.size()),
_baseType
, "DEDUCED"
);
}
}

// Keep only fingerprints containing this field
private static void prune(List<BitSet> candidates, int bit) {
for (Iterator<BitSet> iter = candidates.iterator(); iter.hasNext(); ) {
if (!iter.next().get(bit)) {
iter.remove();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,14 @@
import java.util.Collection;

import com.fasterxml.jackson.annotation.JsonTypeInfo;

import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.annotation.NoClass;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator.Validity;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.jsontype.*;
import com.fasterxml.jackson.databind.util.ClassUtil;

/**
Expand Down Expand Up @@ -332,13 +327,13 @@ protected PolymorphicTypeValidator verifyBaseTypeValidity(MapperConfig<?> config
{
final PolymorphicTypeValidator ptv = subTypeValidator(config);
if (_idType == JsonTypeInfo.Id.CLASS || _idType == JsonTypeInfo.Id.MINIMAL_CLASS) {
final Validity validity = ptv.validateBaseType(config, baseType);
final PolymorphicTypeValidator.Validity validity = ptv.validateBaseType(config, baseType);
// If no subtypes are legal (that is, base type itself is invalid), indicate problem
if (validity == Validity.DENIED) {
if (validity == PolymorphicTypeValidator.Validity.DENIED) {
return reportInvalidBaseType(config, baseType, ptv);
}
// If there's indication that any and all subtypes are fine, replace validator itself:
if (validity == Validity.ALLOWED) {
if (validity == PolymorphicTypeValidator.Validity.ALLOWED) {
return LaissezFaireSubTypeValidator.instance;
}
// otherwise just return validator, is to be called for each distinct type
Expand Down
Loading

0 comments on commit 0a1ba4b

Please sign in to comment.