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

com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector Maven / Gradle / Ivy

The newest version!
package com.fasterxml.jackson.databind.introspect;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.*;

import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.*;
import com.fasterxml.jackson.databind.cfg.HandlerInstantiator;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.ext.Java7Support;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
import com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.VirtualBeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.impl.AttributePropertyWriter;
import com.fasterxml.jackson.databind.ser.std.RawSerializer;
import com.fasterxml.jackson.databind.type.MapLikeType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.util.*;

/**
 * {@link AnnotationIntrospector} implementation that handles standard
 * Jackson annotations.
 */
public class JacksonAnnotationIntrospector
    extends AnnotationIntrospector
    implements java.io.Serializable
{
    private static final long serialVersionUID = 1L;

    @SuppressWarnings("unchecked")
    private final static Class[] ANNOTATIONS_TO_INFER_SER = (Class[])
            new Class[] {
        JsonSerialize.class,
        JsonView.class,
        JsonFormat.class,
        JsonTypeInfo.class,
        JsonRawValue.class,
        JsonUnwrapped.class,
        JsonBackReference.class,
        JsonManagedReference.class
    };

    @SuppressWarnings("unchecked")
    private final static Class[] ANNOTATIONS_TO_INFER_DESER = (Class[])
            new Class[] {
        JsonDeserialize.class,
        JsonView.class,
        JsonFormat.class,
        JsonTypeInfo.class,
        JsonUnwrapped.class,
        JsonBackReference.class,
        JsonManagedReference.class,
        JsonMerge.class // since 2.9
    };

    // NOTE: loading of Java7 dependencies is encapsulated by handlers in Java7Support,
    //  here we do not really need any handling; but for extra-safety use try-catch
    private static final Java7Support _java7Helper;
    static {
        Java7Support x = null;
        try {
            x = Java7Support.instance();
        } catch (Throwable t) {
            ExceptionUtil.rethrowIfFatal(t);
        }
        _java7Helper = x;
    }

    /**
     * Since introspection of annotation types is a performance issue in some
     * use cases (rare, but do exist), let's try a simple cache to reduce
     * need for actual meta-annotation introspection.
     *

* Non-final only because it needs to be re-created after deserialization. *

* Starting with 2.15 key is {@link String} (before that was {@link Class}) * to avoid unintentional retention of third-party annotation class types. * * @since 2.7 */ protected transient LRUMap _annotationsInside = new LRUMap(48, 48); /* /********************************************************** /* Local configuration settings /********************************************************** */ /** * See {@link #setConstructorPropertiesImpliesCreator(boolean)} for * explanation. *

* Defaults to true. * * @since 2.7.4 */ protected boolean _cfgConstructorPropertiesImpliesCreator = true; /* /********************************************************** /* Life-cycle /********************************************************** */ public JacksonAnnotationIntrospector() { } @Override public Version version() { return com.fasterxml.jackson.databind.cfg.PackageVersion.VERSION; } protected Object readResolve() { if (_annotationsInside == null) { _annotationsInside = new LRUMap<>(48, 48); } return this; } /* /********************************************************** /* Configuration /********************************************************** */ /** * Method for changing behavior of {@link java.beans.ConstructorProperties}: * if set to `true`, existence DOES indicate that the given constructor should * be considered a creator; `false` that it should NOT be considered a creator * without explicit use of JsonCreator annotation. *

* Default setting is `true` * * @since 2.7.4 */ public JacksonAnnotationIntrospector setConstructorPropertiesImpliesCreator(boolean b) { _cfgConstructorPropertiesImpliesCreator = b; return this; } /* /********************************************************** /* General annotation properties /********************************************************** */ /** * Annotations with meta-annotation {@link JacksonAnnotationsInside} * are considered bundles. */ @Override public boolean isAnnotationBundle(Annotation ann) { // 22-Sep-2015, tatu: Caching here has modest effect on JavaSE, and only // mostly in degenerate cases where introspection used more often than // it should (like recreating ObjectMapper once per read/write). // But it may be more beneficial on platforms like Android (should verify) final Class type = ann.annotationType(); final String typeName = type.getName(); Boolean b = _annotationsInside.get(typeName); if (b == null) { b = type.getAnnotation(JacksonAnnotationsInside.class) != null; _annotationsInside.putIfAbsent(typeName, b); } return b.booleanValue(); } /* /********************************************************** /* General annotations /********************************************************** */ /** * Since 2.6, we have supported use of {@link JsonProperty} for specifying * explicit serialized name */ @Override @Deprecated // since 2.8 public String findEnumValue(Enum value) { // 11-Jun-2015, tatu: As per [databind#677], need to allow explicit naming. // Unfortunately cannot quite use standard AnnotatedClass here (due to various // reasons, including odd representation JVM uses); has to do for now try { // We know that values are actually static fields with matching name so: Field f = value.getDeclaringClass().getField(value.name()); if (f != null) { JsonProperty prop = f.getAnnotation(JsonProperty.class); if (prop != null) { String n = prop.value(); if (n != null && !n.isEmpty()) { return n; } } } } catch (SecurityException e) { // 17-Sep-2015, tatu: Anything we could/should do here? } catch (NoSuchFieldException e) { // 17-Sep-2015, tatu: should not really happen. But... can we do anything? } return value.name(); } @Override // since 2.7 @Deprecated // since 2.16 public String[] findEnumValues(Class enumType, Enum[] enumValues, String[] names) { HashMap expl = null; for (Field f : enumType.getDeclaredFields()) { if (!f.isEnumConstant()) { continue; } JsonProperty prop = f.getAnnotation(JsonProperty.class); if (prop == null) { continue; } String n = prop.value(); if (n.isEmpty()) { continue; } if (expl == null) { expl = new HashMap(); } expl.put(f.getName(), n); } // and then stitch them together if and as necessary if (expl != null) { for (int i = 0, end = enumValues.length; i < end; ++i) { String defName = enumValues[i].name(); String explValue = expl.get(defName); if (explValue != null) { names[i] = explValue; } } } return names; } @Override // since 2.16 public String[] findEnumValues(MapperConfig config, AnnotatedClass annotatedClass, Enum[] enumValues, String[] names) { Map enumToPropertyMap = new LinkedHashMap(); for (AnnotatedField field : annotatedClass.fields()) { JsonProperty property = field.getAnnotation(JsonProperty.class); if (property != null) { String propValue = property.value(); if (propValue != null && !propValue.isEmpty()) { enumToPropertyMap.put(field.getName(), propValue); } } } // and then stitch them together if and as necessary for (int i = 0, end = enumValues.length; i < end; ++i) { String defName = enumValues[i].name(); String explValue = enumToPropertyMap.get(defName); if (explValue != null) { names[i] = explValue; } } return names; } @Override // since 2.11 @Deprecated // since 2.16 public void findEnumAliases(Class enumType, Enum[] enumValues, String[][] aliasList) { // Main complication: discrepancy between Field that represent enum value, // Enum abstraction; joint by name but not reference for (Field f : enumType.getDeclaredFields()) { if (f.isEnumConstant()) { JsonAlias aliasAnnotation = f.getAnnotation(JsonAlias.class); if (aliasAnnotation != null) { String[] aliases = aliasAnnotation.value(); if (aliases.length != 0) { final String name = f.getName(); // Find matching enum (could create Ma for (int i = 0, end = enumValues.length; i < end; ++i) { if (name.equals(enumValues[i].name())) { aliasList[i] = aliases; } } } } } } } @Override public void findEnumAliases(MapperConfig config, AnnotatedClass annotatedClass, Enum[] enumValues, String[][] aliasList) { HashMap enumToAliasMap = new HashMap<>(); for (AnnotatedField field : annotatedClass.fields()) { JsonAlias alias = field.getAnnotation(JsonAlias.class); if (alias != null) { enumToAliasMap.putIfAbsent(field.getName(), alias.value()); } } for (int i = 0, end = enumValues.length; i < end; ++i) { Enum enumValue = enumValues[i]; aliasList[i] = enumToAliasMap.getOrDefault(enumValue.name(), new String[]{}); } } @Override @Deprecated // since 2.16 public Enum findDefaultEnumValue(Class> enumCls) { return ClassUtil.findFirstAnnotatedEnumValue(enumCls, JsonEnumDefaultValue.class); } /** * Finds the Enum value that should be considered the default value, if possible. *

* This implementation relies on {@link JsonEnumDefaultValue} annotation to determine the default value if present. * * @param annotatedClass The Enum class to scan for the default value annotation. * @return null if none found or it's not possible to determine one. * @since 2.16 */ @Override // since 2.16 public Enum findDefaultEnumValue(AnnotatedClass annotatedClass, Enum[] enumValues) { for (Annotated field : annotatedClass.fields()) { if (!field.getType().isEnumType()) { continue; } JsonEnumDefaultValue found = _findAnnotation(field, JsonEnumDefaultValue.class); if (found == null) { continue; } // Return the "first" enum with annotation for (Enum enumValue : enumValues) { if (enumValue.name().equals(field.getName())) { return enumValue; } } } return null; } /* /********************************************************** /* General class annotations /********************************************************** */ @Override public PropertyName findRootName(AnnotatedClass ac) { JsonRootName ann = _findAnnotation(ac, JsonRootName.class); if (ann == null) { return null; } String ns = ann.namespace(); if (ns != null && ns.isEmpty()) { ns = null; } return PropertyName.construct(ann.value(), ns); } @Override public Boolean isIgnorableType(AnnotatedClass ac) { JsonIgnoreType ignore = _findAnnotation(ac, JsonIgnoreType.class); return (ignore == null) ? null : ignore.value(); } @Override // since 2.12 public JsonIgnoreProperties.Value findPropertyIgnoralByName(MapperConfig config, Annotated a) { JsonIgnoreProperties v = _findAnnotation(a, JsonIgnoreProperties.class); if (v == null) { return JsonIgnoreProperties.Value.empty(); } return JsonIgnoreProperties.Value.from(v); } // Keep around until 3.x @Deprecated // since 2.12 @Override public JsonIgnoreProperties.Value findPropertyIgnorals(Annotated ac) { return findPropertyIgnoralByName(null, ac); } @Override public JsonIncludeProperties.Value findPropertyInclusionByName(MapperConfig config, Annotated a) { JsonIncludeProperties v = _findAnnotation(a, JsonIncludeProperties.class); if (v == null) { return JsonIncludeProperties.Value.all(); } return JsonIncludeProperties.Value.from(v); } @Override public Object findFilterId(Annotated a) { JsonFilter ann = _findAnnotation(a, JsonFilter.class); if (ann != null) { String id = ann.value(); // Empty String is same as not having annotation, to allow overrides if (!id.isEmpty()) { return id; } } return null; } @Override public Object findNamingStrategy(AnnotatedClass ac) { JsonNaming ann = _findAnnotation(ac, JsonNaming.class); return (ann == null) ? null : ann.value(); } @Override public Object findEnumNamingStrategy(MapperConfig config, AnnotatedClass ac) { EnumNaming ann = _findAnnotation(ac, EnumNaming.class); return (ann == null) ? null : ann.value(); } @Override public String findClassDescription(AnnotatedClass ac) { JsonClassDescription ann = _findAnnotation(ac, JsonClassDescription.class); return (ann == null) ? null : ann.value(); } /* /********************************************************** /* Property auto-detection /********************************************************** */ @Override public VisibilityChecker findAutoDetectVisibility(AnnotatedClass ac, VisibilityChecker checker) { JsonAutoDetect ann = _findAnnotation(ac, JsonAutoDetect.class); return (ann == null) ? checker : checker.with(ann); } /* /********************************************************** /* General member (field, method/constructor) annotations /********************************************************** */ @Override public String findImplicitPropertyName(AnnotatedMember m) { PropertyName n = _findConstructorName(m); return (n == null) ? null : n.getSimpleName(); } @Override public List findPropertyAliases(Annotated m) { JsonAlias ann = _findAnnotation(m, JsonAlias.class); if (ann == null) { return null; } String[] strs = ann.value(); final int len = strs.length; if (len == 0) { return Collections.emptyList(); } List result = new ArrayList<>(len); for (int i = 0; i < len; ++i) { result.add(PropertyName.construct(strs[i])); } return result; } @Override public boolean hasIgnoreMarker(AnnotatedMember m) { return _isIgnorable(m); } @Override public Boolean hasRequiredMarker(AnnotatedMember m) { JsonProperty ann = _findAnnotation(m, JsonProperty.class); if (ann != null) { return ann.required(); } return null; } @Override public JsonProperty.Access findPropertyAccess(Annotated m) { JsonProperty ann = _findAnnotation(m, JsonProperty.class); if (ann != null) { return ann.access(); } return null; } @Override public String findPropertyDescription(Annotated ann) { JsonPropertyDescription desc = _findAnnotation(ann, JsonPropertyDescription.class); return (desc == null) ? null : desc.value(); } @Override public Integer findPropertyIndex(Annotated ann) { JsonProperty prop = _findAnnotation(ann, JsonProperty.class); if (prop != null) { int ix = prop.index(); if (ix != JsonProperty.INDEX_UNKNOWN) { return Integer.valueOf(ix); } } return null; } @Override public String findPropertyDefaultValue(Annotated ann) { JsonProperty prop = _findAnnotation(ann, JsonProperty.class); if (prop == null) { return null; } String str = prop.defaultValue(); // Since annotations do not allow nulls, need to assume empty means "none" return str.isEmpty() ? null : str; } @Override public JsonFormat.Value findFormat(Annotated ann) { JsonFormat f = _findAnnotation(ann, JsonFormat.class); // NOTE: could also just call `JsonFormat.Value.from()` with `null` // too, but that returns "empty" instance return (f == null) ? null : JsonFormat.Value.from(f); } @Override public ReferenceProperty findReferenceType(AnnotatedMember member) { JsonManagedReference ref1 = _findAnnotation(member, JsonManagedReference.class); if (ref1 != null) { return AnnotationIntrospector.ReferenceProperty.managed(ref1.value()); } JsonBackReference ref2 = _findAnnotation(member, JsonBackReference.class); if (ref2 != null) { return AnnotationIntrospector.ReferenceProperty.back(ref2.value()); } return null; } @Override public NameTransformer findUnwrappingNameTransformer(AnnotatedMember member) { JsonUnwrapped ann = _findAnnotation(member, JsonUnwrapped.class); // if not enabled, just means annotation is not enabled; not necessarily // that unwrapping should not be done (relevant when using chained introspectors) if (ann == null || !ann.enabled()) { return null; } String prefix = ann.prefix(); String suffix = ann.suffix(); return NameTransformer.simpleTransformer(prefix, suffix); } @Override // since 2.9 public JacksonInject.Value findInjectableValue(AnnotatedMember m) { JacksonInject ann = _findAnnotation(m, JacksonInject.class); if (ann == null) { return null; } // Empty String means that we should use name of declared value class. JacksonInject.Value v = JacksonInject.Value.from(ann); if (!v.hasId()) { Object id; // slight complication; for setters, type if (!(m instanceof AnnotatedMethod)) { id = m.getRawType().getName(); } else { AnnotatedMethod am = (AnnotatedMethod) m; if (am.getParameterCount() == 0) { // getter id = m.getRawType().getName(); } else { // setter id = am.getRawParameterType(0).getName(); } } v = v.withId(id); } return v; } @Override @Deprecated // since 2.9 public Object findInjectableValueId(AnnotatedMember m) { JacksonInject.Value v = findInjectableValue(m); return (v == null) ? null : v.getId(); } @Override public Class[] findViews(Annotated a) { JsonView ann = _findAnnotation(a, JsonView.class); return (ann == null) ? null : ann.value(); } /** * Specific implementation that will use following tie-breaker on * given setter parameter types: *

    *
  1. If either one is primitive type then either return {@code null} * (both primitives) or one that is primitive (when only primitive) *
  2. *
  3. If only one is of type {@code String}, return that setter *
  4. *
  5. Otherwise return {@code null} *
  6. *
* Returning {@code null} will indicate that resolution could not be done. */ @Override // since 2.7 public AnnotatedMethod resolveSetterConflict(MapperConfig config, AnnotatedMethod setter1, AnnotatedMethod setter2) { Class cls1 = setter1.getRawParameterType(0); Class cls2 = setter2.getRawParameterType(0); // First: prefer primitives over non-primitives // 11-Dec-2015, tatu: TODO, perhaps consider wrappers for primitives too? if (cls1.isPrimitive()) { if (!cls2.isPrimitive()) { return setter1; } // 10-May-2021, tatu: if both primitives cannot decide return null; } else if (cls2.isPrimitive()) { return setter2; } if (cls1 == String.class) { if (cls2 != String.class) { return setter1; } } else if (cls2 == String.class) { return setter2; } return null; } @Override // since 2.11 public PropertyName findRenameByField(MapperConfig config, AnnotatedField f, PropertyName implName) { // Nothing to report, only used by modules. But define just as documentation return null; } /* /********************************************************** /* Annotations for Polymorphic Type handling /********************************************************** */ /** * @since 2.16 (backported from Jackson 3.0) */ @Override public JsonTypeInfo.Value findPolymorphicTypeInfo(MapperConfig config, Annotated ann) { JsonTypeInfo t = _findAnnotation(ann, JsonTypeInfo.class); return (t == null) ? null : JsonTypeInfo.Value.from(t); } @Override public TypeResolverBuilder findTypeResolver(MapperConfig config, AnnotatedClass ac, JavaType baseType) { return _findTypeResolver(config, ac, baseType); } @Override public TypeResolverBuilder findPropertyTypeResolver(MapperConfig config, AnnotatedMember am, JavaType baseType) { // As per definition of @JsonTypeInfo, should only apply to contents of container // (collection, map) types, not container types themselves: // 17-Apr-2016, tatu: For 2.7.4 make sure ReferenceType also included if (baseType.isContainerType() || baseType.isReferenceType()) { return null; } // No per-member type overrides (yet) return _findTypeResolver(config, am, baseType); } @Override public TypeResolverBuilder findPropertyContentTypeResolver(MapperConfig config, AnnotatedMember am, JavaType containerType) { // First: let's ensure property is a container type: caller should have // verified but just to be sure if (containerType.getContentType() == null) { throw new IllegalArgumentException("Must call method with a container or reference type (got "+containerType+")"); } return _findTypeResolver(config, am, containerType); } @Override public List findSubtypes(Annotated a) { JsonSubTypes t = _findAnnotation(a, JsonSubTypes.class); if (t == null) return null; JsonSubTypes.Type[] types = t.value(); // 02-Aug-2022, tatu: As per [databind#3500], may need to check uniqueness // of names if (t.failOnRepeatedNames()) { return findSubtypesCheckRepeatedNames(a.getName(), types); } else { ArrayList result = new ArrayList(types.length); for (JsonSubTypes.Type type : types) { result.add(new NamedType(type.value(), type.name())); // [databind#2761]: alternative set of names to use for (String name : type.names()) { result.add(new NamedType(type.value(), name)); } } return result; } } // @since 2.14 private List findSubtypesCheckRepeatedNames(String annotatedTypeName, JsonSubTypes.Type[] types) { ArrayList result = new ArrayList(types.length); Set seenNames = new HashSet<>(); for (JsonSubTypes.Type type : types) { final String typeName = type.name(); if (!typeName.isEmpty() && seenNames.contains(typeName)) { throw new IllegalArgumentException("Annotated type [" + annotatedTypeName + "] got repeated subtype name [" + typeName + "]"); } else { seenNames.add(typeName); } result.add(new NamedType(type.value(), typeName)); // [databind#2761]: alternative set of names to use for (String altName : type.names()) { if (!altName.isEmpty() && seenNames.contains(altName)) { throw new IllegalArgumentException("Annotated type [" + annotatedTypeName + "] got repeated subtype name [" + altName + "]"); } else { seenNames.add(altName); } result.add(new NamedType(type.value(), altName)); } } return result; } @Override public String findTypeName(AnnotatedClass ac) { JsonTypeName tn = _findAnnotation(ac, JsonTypeName.class); return (tn == null) ? null : tn.value(); } @Override public Boolean isTypeId(AnnotatedMember member) { return _hasAnnotation(member, JsonTypeId.class); } /* /********************************************************** /* Annotations for Object Id handling /********************************************************** */ @Override public ObjectIdInfo findObjectIdInfo(Annotated ann) { JsonIdentityInfo info = _findAnnotation(ann, JsonIdentityInfo.class); if (info == null || info.generator() == ObjectIdGenerators.None.class) { return null; } // In future may need to allow passing namespace? PropertyName name = PropertyName.construct(info.property()); return new ObjectIdInfo(name, info.scope(), info.generator(), info.resolver()); } @Override public ObjectIdInfo findObjectReferenceInfo(Annotated ann, ObjectIdInfo objectIdInfo) { JsonIdentityReference ref = _findAnnotation(ann, JsonIdentityReference.class); if (ref == null) { return objectIdInfo; } if (objectIdInfo == null) { objectIdInfo = ObjectIdInfo.empty(); } return objectIdInfo.withAlwaysAsId(ref.alwaysAsId()); } /* /********************************************************** /* Serialization: general annotations /********************************************************** */ @Override public Object findSerializer(Annotated a) { JsonSerialize ann = _findAnnotation(a, JsonSerialize.class); if (ann != null) { @SuppressWarnings("rawtypes") Class serClass = ann.using(); if (serClass != JsonSerializer.None.class) { return serClass; } } /* 18-Oct-2010, tatu: [JACKSON-351] @JsonRawValue handled just here, for now; * if we need to get raw indicator from other sources need to add * separate accessor within {@link AnnotationIntrospector} interface. */ JsonRawValue annRaw = _findAnnotation(a, JsonRawValue.class); if ((annRaw != null) && annRaw.value()) { // let's construct instance with nominal type: Class cls = a.getRawType(); return new RawSerializer(cls); } return null; } @Override public Object findKeySerializer(Annotated a) { JsonSerialize ann = _findAnnotation(a, JsonSerialize.class); if (ann != null) { @SuppressWarnings("rawtypes") Class serClass = ann.keyUsing(); if (serClass != JsonSerializer.None.class) { return serClass; } } return null; } @Override public Object findContentSerializer(Annotated a) { JsonSerialize ann = _findAnnotation(a, JsonSerialize.class); if (ann != null) { @SuppressWarnings("rawtypes") Class serClass = ann.contentUsing(); if (serClass != JsonSerializer.None.class) { return serClass; } } return null; } @Override public Object findNullSerializer(Annotated a) { JsonSerialize ann = _findAnnotation(a, JsonSerialize.class); if (ann != null) { @SuppressWarnings("rawtypes") Class serClass = ann.nullsUsing(); if (serClass != JsonSerializer.None.class) { return serClass; } } return null; } @Override public JsonInclude.Value findPropertyInclusion(Annotated a) { JsonInclude inc = _findAnnotation(a, JsonInclude.class); JsonInclude.Value value = (inc == null) ? JsonInclude.Value.empty() : JsonInclude.Value.from(inc); // only consider deprecated variant if we didn't have non-deprecated one: if (value.getValueInclusion() == JsonInclude.Include.USE_DEFAULTS) { value = _refinePropertyInclusion(a, value); } return value; } @SuppressWarnings("deprecation") private JsonInclude.Value _refinePropertyInclusion(Annotated a, JsonInclude.Value value) { JsonSerialize ann = _findAnnotation(a, JsonSerialize.class); if (ann != null) { switch (ann.include()) { case ALWAYS: return value.withValueInclusion(JsonInclude.Include.ALWAYS); case NON_NULL: return value.withValueInclusion(JsonInclude.Include.NON_NULL); case NON_DEFAULT: return value.withValueInclusion(JsonInclude.Include.NON_DEFAULT); case NON_EMPTY: return value.withValueInclusion(JsonInclude.Include.NON_EMPTY); case DEFAULT_INCLUSION: default: } } return value; } @Override public JsonSerialize.Typing findSerializationTyping(Annotated a) { JsonSerialize ann = _findAnnotation(a, JsonSerialize.class); return (ann == null) ? null : ann.typing(); } @Override public Object findSerializationConverter(Annotated a) { JsonSerialize ann = _findAnnotation(a, JsonSerialize.class); return (ann == null) ? null : _classIfExplicit(ann.converter(), Converter.None.class); } @Override public Object findSerializationContentConverter(AnnotatedMember a) { JsonSerialize ann = _findAnnotation(a, JsonSerialize.class); return (ann == null) ? null : _classIfExplicit(ann.contentConverter(), Converter.None.class); } /* /********************************************************** /* Serialization: type refinements /********************************************************** */ @Override public JavaType refineSerializationType(final MapperConfig config, final Annotated a, final JavaType baseType) throws JsonMappingException { JavaType type = baseType; final TypeFactory tf = config.getTypeFactory(); final JsonSerialize jsonSer = _findAnnotation(a, JsonSerialize.class); // Ok: start by refining the main type itself; common to all types final Class serClass = (jsonSer == null) ? null : _classIfExplicit(jsonSer.as()); if (serClass != null) { if (type.hasRawClass(serClass)) { // 30-Nov-2015, tatu: As per [databind#1023], need to allow forcing of // static typing this way type = type.withStaticTyping(); } else { Class currRaw = type.getRawClass(); try { // 11-Oct-2015, tatu: For deser, we call `TypeFactory.constructSpecializedType()`, // may be needed here too in future? if (serClass.isAssignableFrom(currRaw)) { // common case type = tf.constructGeneralizedType(type, serClass); } else if (currRaw.isAssignableFrom(serClass)) { // specialization, ok as well type = tf.constructSpecializedType(type, serClass); } else if (_primitiveAndWrapper(currRaw, serClass)) { // 27-Apr-2017, tatu: [databind#1592] ignore primitive<->wrapper refinements type = type.withStaticTyping(); } else { throw _databindException( String.format("Cannot refine serialization type %s into %s; types not related", type, serClass.getName())); } } catch (IllegalArgumentException iae) { throw _databindException(iae, String.format("Failed to widen type %s with annotation (value %s), from '%s': %s", type, serClass.getName(), a.getName(), iae.getMessage())); } } } // Then further processing for container types // First, key type (for Maps, Map-like types): if (type.isMapLikeType()) { JavaType keyType = type.getKeyType(); final Class keyClass = (jsonSer == null) ? null : _classIfExplicit(jsonSer.keyAs()); if (keyClass != null) { if (keyType.hasRawClass(keyClass)) { keyType = keyType.withStaticTyping(); } else { Class currRaw = keyType.getRawClass(); try { // 19-May-2016, tatu: As per [databind#1231], [databind#1178] may need to actually // specialize (narrow) type sometimes, even if more commonly opposite // is needed. if (keyClass.isAssignableFrom(currRaw)) { // common case keyType = tf.constructGeneralizedType(keyType, keyClass); } else if (currRaw.isAssignableFrom(keyClass)) { // specialization, ok as well keyType = tf.constructSpecializedType(keyType, keyClass); } else if (_primitiveAndWrapper(currRaw, keyClass)) { // 27-Apr-2017, tatu: [databind#1592] ignore primitive<->wrapper refinements keyType = keyType.withStaticTyping(); } else { throw _databindException( String.format("Cannot refine serialization key type %s into %s; types not related", keyType, keyClass.getName())); } } catch (IllegalArgumentException iae) { throw _databindException(iae, String.format("Failed to widen key type of %s with concrete-type annotation (value %s), from '%s': %s", type, keyClass.getName(), a.getName(), iae.getMessage())); } } type = ((MapLikeType) type).withKeyType(keyType); } } JavaType contentType = type.getContentType(); if (contentType != null) { // collection[like], map[like], array, reference // And then value types for all containers: final Class contentClass = (jsonSer == null) ? null : _classIfExplicit(jsonSer.contentAs()); if (contentClass != null) { if (contentType.hasRawClass(contentClass)) { contentType = contentType.withStaticTyping(); } else { // 03-Apr-2016, tatu: As per [databind#1178], may need to actually // specialize (narrow) type sometimes, even if more commonly opposite // is needed. Class currRaw = contentType.getRawClass(); try { if (contentClass.isAssignableFrom(currRaw)) { // common case contentType = tf.constructGeneralizedType(contentType, contentClass); } else if (currRaw.isAssignableFrom(contentClass)) { // specialization, ok as well contentType = tf.constructSpecializedType(contentType, contentClass); } else if (_primitiveAndWrapper(currRaw, contentClass)) { // 27-Apr-2017, tatu: [databind#1592] ignore primitive<->wrapper refinements contentType = contentType.withStaticTyping(); } else { throw _databindException( String.format("Cannot refine serialization content type %s into %s; types not related", contentType, contentClass.getName())); } } catch (IllegalArgumentException iae) { // shouldn't really happen throw _databindException(iae, String.format("Internal error: failed to refine value type of %s with concrete-type annotation (value %s), from '%s': %s", type, contentClass.getName(), a.getName(), iae.getMessage())); } } type = type.withContentType(contentType); } } return type; } /* /********************************************************** /* Serialization: class annotations /********************************************************** */ @Override public String[] findSerializationPropertyOrder(AnnotatedClass ac) { JsonPropertyOrder order = _findAnnotation(ac, JsonPropertyOrder.class); return (order == null) ? null : order.value(); } @Override public Boolean findSerializationSortAlphabetically(Annotated ann) { return _findSortAlpha(ann); } private final Boolean _findSortAlpha(Annotated ann) { JsonPropertyOrder order = _findAnnotation(ann, JsonPropertyOrder.class); // 23-Jun-2015, tatu: as per [databind#840], let's only consider // `true` to have any significance. if ((order != null) && order.alphabetic()) { return Boolean.TRUE; } return null; } @Override public void findAndAddVirtualProperties(MapperConfig config, AnnotatedClass ac, List properties) { JsonAppend ann = _findAnnotation(ac, JsonAppend.class); if (ann == null) { return; } final boolean prepend = ann.prepend(); JavaType propType = null; // First: any attribute-backed properties? JsonAppend.Attr[] attrs = ann.attrs(); for (int i = 0, len = attrs.length; i < len; ++i) { if (propType == null) { propType = config.constructType(Object.class); } BeanPropertyWriter bpw = _constructVirtualProperty(attrs[i], config, ac, propType); if (prepend) { properties.add(i, bpw); } else { properties.add(bpw); } } // Then: general-purpose virtual properties? JsonAppend.Prop[] props = ann.props(); for (int i = 0, len = props.length; i < len; ++i) { BeanPropertyWriter bpw = _constructVirtualProperty(props[i], config, ac); if (prepend) { properties.add(i, bpw); } else { properties.add(bpw); } } } protected BeanPropertyWriter _constructVirtualProperty(JsonAppend.Attr attr, MapperConfig config, AnnotatedClass ac, JavaType type) { PropertyMetadata metadata = attr.required() ? PropertyMetadata.STD_REQUIRED : PropertyMetadata.STD_OPTIONAL; // could add Index, Description in future, if those matter String attrName = attr.value(); // allow explicit renaming; if none, default to attribute name PropertyName propName = _propertyName(attr.propName(), attr.propNamespace()); if (!propName.hasSimpleName()) { propName = PropertyName.construct(attrName); } // now, then, we need a placeholder for member (no real Field/Method): AnnotatedMember member = new VirtualAnnotatedMember(ac, ac.getRawType(), attrName, type); // and with that and property definition SimpleBeanPropertyDefinition propDef = SimpleBeanPropertyDefinition.construct(config, member, propName, metadata, attr.include()); // can construct the property writer return AttributePropertyWriter.construct(attrName, propDef, ac.getAnnotations(), type); } protected BeanPropertyWriter _constructVirtualProperty(JsonAppend.Prop prop, MapperConfig config, AnnotatedClass ac) { PropertyMetadata metadata = prop.required() ? PropertyMetadata.STD_REQUIRED : PropertyMetadata.STD_OPTIONAL; PropertyName propName = _propertyName(prop.name(), prop.namespace()); JavaType type = config.constructType(prop.type()); // now, then, we need a placeholder for member (no real Field/Method): AnnotatedMember member = new VirtualAnnotatedMember(ac, ac.getRawType(), propName.getSimpleName(), type); // and with that and property definition SimpleBeanPropertyDefinition propDef = SimpleBeanPropertyDefinition.construct(config, member, propName, metadata, prop.include()); Class implClass = prop.value(); HandlerInstantiator hi = config.getHandlerInstantiator(); VirtualBeanPropertyWriter bpw = (hi == null) ? null : hi.virtualPropertyWriterInstance(config, implClass); if (bpw == null) { bpw = (VirtualBeanPropertyWriter) ClassUtil.createInstance(implClass, config.canOverrideAccessModifiers()); } // one more thing: give it necessary contextual information return bpw.withConfig(config, ac, propDef, type); } /* /********************************************************** /* Serialization: property annotations /********************************************************** */ @Override public PropertyName findNameForSerialization(Annotated a) { boolean useDefault = false; JsonGetter jg = _findAnnotation(a, JsonGetter.class); if (jg != null) { String s = jg.value(); // 04-May-2018, tatu: Should allow for "nameless" `@JsonGetter` too if (!s.isEmpty()) { return PropertyName.construct(s); } useDefault = true; } JsonProperty pann = _findAnnotation(a, JsonProperty.class); if (pann != null) { // 14-Nov-2020, tatu: "namespace" added in 2.12 String ns = pann.namespace(); if (ns != null && ns.isEmpty()) { ns = null; } return PropertyName.construct(pann.value(), ns); } if (useDefault || _hasOneOf(a, ANNOTATIONS_TO_INFER_SER)) { return PropertyName.USE_DEFAULT; } return null; } @Override // since 2.12 public Boolean hasAsKey(MapperConfig config, Annotated a) { JsonKey ann = _findAnnotation(a, JsonKey.class); if (ann == null) { return null; } return ann.value(); } @Override // since 2.9 public Boolean hasAsValue(Annotated a) { JsonValue ann = _findAnnotation(a, JsonValue.class); if (ann == null) { return null; } return ann.value(); } @Override // since 2.9 public Boolean hasAnyGetter(Annotated a) { JsonAnyGetter ann = _findAnnotation(a, JsonAnyGetter.class); if (ann == null) { return null; } return ann.enabled(); } @Override @Deprecated // since 2.9 public boolean hasAnyGetterAnnotation(AnnotatedMethod am) { // No dedicated disabling; regular @JsonIgnore used if needs to be ignored (handled separately) return _hasAnnotation(am, JsonAnyGetter.class); } @Override @Deprecated // since 2.9 public boolean hasAsValueAnnotation(AnnotatedMethod am) { JsonValue ann = _findAnnotation(am, JsonValue.class); // value of 'false' means disabled... return (ann != null) && ann.value(); } /* /********************************************************** /* Deserialization: general annotations /********************************************************** */ @Override public Object findDeserializer(Annotated a) { JsonDeserialize ann = _findAnnotation(a, JsonDeserialize.class); if (ann != null) { @SuppressWarnings("rawtypes") Class deserClass = ann.using(); if (deserClass != JsonDeserializer.None.class) { return deserClass; } } return null; } @Override public Object findKeyDeserializer(Annotated a) { JsonDeserialize ann = _findAnnotation(a, JsonDeserialize.class); if (ann != null) { Class deserClass = ann.keyUsing(); if (deserClass != KeyDeserializer.None.class) { return deserClass; } } return null; } @Override public Object findContentDeserializer(Annotated a) { JsonDeserialize ann = _findAnnotation(a, JsonDeserialize.class); if (ann != null) { @SuppressWarnings("rawtypes") Class deserClass = ann.contentUsing(); if (deserClass != JsonDeserializer.None.class) { return deserClass; } } return null; } @Override public Object findDeserializationConverter(Annotated a) { JsonDeserialize ann = _findAnnotation(a, JsonDeserialize.class); return (ann == null) ? null : _classIfExplicit(ann.converter(), Converter.None.class); } @Override public Object findDeserializationContentConverter(AnnotatedMember a) { JsonDeserialize ann = _findAnnotation(a, JsonDeserialize.class); return (ann == null) ? null : _classIfExplicit(ann.contentConverter(), Converter.None.class); } /* /********************************************************** /* Deserialization: type modifications /********************************************************** */ @Override public JavaType refineDeserializationType(final MapperConfig config, final Annotated a, final JavaType baseType) throws JsonMappingException { JavaType type = baseType; final TypeFactory tf = config.getTypeFactory(); final JsonDeserialize jsonDeser = _findAnnotation(a, JsonDeserialize.class); // Ok: start by refining the main type itself; common to all types final Class valueClass = (jsonDeser == null) ? null : _classIfExplicit(jsonDeser.as()); if ((valueClass != null) && !type.hasRawClass(valueClass) && !_primitiveAndWrapper(type, valueClass)) { try { type = tf.constructSpecializedType(type, valueClass); } catch (IllegalArgumentException iae) { throw _databindException(iae, String.format("Failed to narrow type %s with annotation (value %s), from '%s': %s", type, valueClass.getName(), a.getName(), iae.getMessage())); } } // Then further processing for container types // First, key type (for Maps, Map-like types): if (type.isMapLikeType()) { JavaType keyType = type.getKeyType(); final Class keyClass = (jsonDeser == null) ? null : _classIfExplicit(jsonDeser.keyAs()); if ((keyClass != null) && !_primitiveAndWrapper(keyType, keyClass)) { try { keyType = tf.constructSpecializedType(keyType, keyClass); type = ((MapLikeType) type).withKeyType(keyType); } catch (IllegalArgumentException iae) { throw _databindException(iae, String.format("Failed to narrow key type of %s with concrete-type annotation (value %s), from '%s': %s", type, keyClass.getName(), a.getName(), iae.getMessage())); } } } JavaType contentType = type.getContentType(); if (contentType != null) { // collection[like], map[like], array, reference // And then value types for all containers: final Class contentClass = (jsonDeser == null) ? null : _classIfExplicit(jsonDeser.contentAs()); if ((contentClass != null) && !_primitiveAndWrapper(contentType, contentClass)) { try { contentType = tf.constructSpecializedType(contentType, contentClass); type = type.withContentType(contentType); } catch (IllegalArgumentException iae) { throw _databindException(iae, String.format("Failed to narrow value type of %s with concrete-type annotation (value %s), from '%s': %s", type, contentClass.getName(), a.getName(), iae.getMessage())); } } } return type; } /* /********************************************************** /* Deserialization: Class annotations /********************************************************** */ @Override public Object findValueInstantiator(AnnotatedClass ac) { JsonValueInstantiator ann = _findAnnotation(ac, JsonValueInstantiator.class); // no 'null' marker yet, so: return (ann == null) ? null : ann.value(); } @Override public Class findPOJOBuilder(AnnotatedClass ac) { JsonDeserialize ann = _findAnnotation(ac, JsonDeserialize.class); return (ann == null) ? null : _classIfExplicit(ann.builder()); } @Override public JsonPOJOBuilder.Value findPOJOBuilderConfig(AnnotatedClass ac) { JsonPOJOBuilder ann = _findAnnotation(ac, JsonPOJOBuilder.class); return (ann == null) ? null : new JsonPOJOBuilder.Value(ann); } /* /********************************************************** /* Deserialization: property annotations /********************************************************** */ @Override public PropertyName findNameForDeserialization(Annotated a) { // @JsonSetter has precedence over @JsonProperty, being more specific boolean useDefault = false; JsonSetter js = _findAnnotation(a, JsonSetter.class); if (js != null) { String s = js.value(); // 04-May-2018, tatu: Need to allow for "nameless" `@JsonSetter` too if (s.isEmpty()) { useDefault = true; } else { return PropertyName.construct(s); } } JsonProperty pann = _findAnnotation(a, JsonProperty.class); if (pann != null) { // 14-Nov-2020, tatu: "namespace" added in 2.12 String ns = pann.namespace(); if (ns != null && ns.isEmpty()) { ns = null; } return PropertyName.construct(pann.value(), ns); } if (useDefault || _hasOneOf(a, ANNOTATIONS_TO_INFER_DESER)) { return PropertyName.USE_DEFAULT; } return null; } @Override public Boolean hasAnySetter(Annotated a) { JsonAnySetter ann = _findAnnotation(a, JsonAnySetter.class); return (ann == null) ? null : ann.enabled(); } @Override public JsonSetter.Value findSetterInfo(Annotated a) { return JsonSetter.Value.from(_findAnnotation(a, JsonSetter.class)); } @Override // since 2.9 public Boolean findMergeInfo(Annotated a) { JsonMerge ann = _findAnnotation(a, JsonMerge.class); return (ann == null) ? null : ann.value().asBoolean(); } @Override @Deprecated // since 2.9 public boolean hasAnySetterAnnotation(AnnotatedMethod am) { return _hasAnnotation(am, JsonAnySetter.class); } @Override @Deprecated // since 2.9 public boolean hasCreatorAnnotation(Annotated a) { /* No dedicated disabling; regular @JsonIgnore used if needs to be * ignored (and if so, is handled prior to this method getting called) */ JsonCreator ann = _findAnnotation(a, JsonCreator.class); if (ann != null) { return (ann.mode() != JsonCreator.Mode.DISABLED); } // 19-Apr-2016, tatu: As per [databind#1197], [databind#1122] (and some related), // may or may not consider it a creator if (_cfgConstructorPropertiesImpliesCreator ) { if (a instanceof AnnotatedConstructor) { if (_java7Helper != null) { Boolean b = _java7Helper.hasCreatorAnnotation(a); if (b != null) { return b.booleanValue(); } } } } return false; } @Override @Deprecated // since 2.9 public JsonCreator.Mode findCreatorBinding(Annotated a) { JsonCreator ann = _findAnnotation(a, JsonCreator.class); return (ann == null) ? null : ann.mode(); } @Override public JsonCreator.Mode findCreatorAnnotation(MapperConfig config, Annotated a) { JsonCreator ann = _findAnnotation(a, JsonCreator.class); if (ann != null) { return ann.mode(); } if (_cfgConstructorPropertiesImpliesCreator && config.isEnabled(MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES) ) { if (a instanceof AnnotatedConstructor) { if (_java7Helper != null) { Boolean b = _java7Helper.hasCreatorAnnotation(a); if ((b != null) && b.booleanValue()) { // 13-Sep-2016, tatu: Judgment call, but I don't think JDK ever implies // use of delegate; assumes as-properties implicitly return JsonCreator.Mode.PROPERTIES; } } } } return null; } /* /********************************************************** /* Helper methods /********************************************************** */ protected boolean _isIgnorable(Annotated a) { JsonIgnore ann = _findAnnotation(a, JsonIgnore.class); if (ann != null) { return ann.value(); } if (_java7Helper != null) { Boolean b = _java7Helper.findTransient(a); if (b != null) { return b.booleanValue(); } } return false; } protected Class _classIfExplicit(Class cls) { if (cls == null || ClassUtil.isBogusClass(cls)) { return null; } return cls; } protected Class _classIfExplicit(Class cls, Class implicit) { cls = _classIfExplicit(cls); return (cls == null || cls == implicit) ? null : cls; } protected PropertyName _propertyName(String localName, String namespace) { if (localName.isEmpty()) { return PropertyName.USE_DEFAULT; } if (namespace == null || namespace.isEmpty()) { return PropertyName.construct(localName); } return PropertyName.construct(localName, namespace); } protected PropertyName _findConstructorName(Annotated a) { if (a instanceof AnnotatedParameter) { AnnotatedParameter p = (AnnotatedParameter) a; AnnotatedWithParams ctor = p.getOwner(); if (ctor != null) { if (_java7Helper != null) { PropertyName name = _java7Helper.findConstructorName(p); if (name != null) { return name; } } } } return null; } /** * Helper method called to construct and initialize instance of {@link TypeResolverBuilder} * if given annotated element indicates one is needed. */ @SuppressWarnings("deprecation") protected TypeResolverBuilder _findTypeResolver(MapperConfig config, Annotated ann, JavaType baseType) { // since 2.16 : backporting {@link JsonTypeInfo.Value} from 3.0 JsonTypeInfo.Value typeInfo = findPolymorphicTypeInfo(config, ann); // First: maybe we have explicit type resolver? TypeResolverBuilder b; JsonTypeResolver resAnn = _findAnnotation(ann, JsonTypeResolver.class); if (resAnn != null) { if (typeInfo == null) { return null; } // let's not try to force access override (would need to pass // settings through if we did, since that's not doable on some platforms) b = config.typeResolverBuilderInstance(ann, resAnn.value()); } else { // if not, use standard one, if indicated by annotations if (typeInfo == null) { return null; } // bit special; must return 'marker' to block use of default typing: if (typeInfo.getIdType() == JsonTypeInfo.Id.NONE) { return _constructNoTypeResolverBuilder(); } b = _constructStdTypeResolverBuilder(config, typeInfo, baseType); } // Does it define a custom type id resolver? JsonTypeIdResolver idResInfo = _findAnnotation(ann, JsonTypeIdResolver.class); TypeIdResolver idRes = (idResInfo == null) ? null : config.typeIdResolverInstance(ann, idResInfo.value()); if (idRes != null) { idRes.init(baseType); } // 13-Aug-2011, tatu: One complication; external id only works for properties; // so if declared for a Class, we will need to map it to "PROPERTY" // instead of "EXTERNAL_PROPERTY" JsonTypeInfo.As inclusion = typeInfo.getInclusionType(); if (inclusion == JsonTypeInfo.As.EXTERNAL_PROPERTY && (ann instanceof AnnotatedClass)) { typeInfo = typeInfo.withInclusionType(JsonTypeInfo.As.PROPERTY); } Class defaultImpl = typeInfo.getDefaultImpl(); // 08-Dec-2014, tatu: To deprecate `JsonTypeInfo.None` we need to use other placeholder(s); // and since `java.util.Void` has other purpose (to indicate "deser as null"), we'll instead // use `JsonTypeInfo.class` itself. But any annotation type will actually do, as they have no // valid use (cannot instantiate as default) if (defaultImpl != null && defaultImpl != JsonTypeInfo.None.class && !defaultImpl.isAnnotation()) { typeInfo = typeInfo.withDefaultImpl(defaultImpl); } b = b.init(typeInfo, idRes); return b; } /** * Helper method for constructing standard {@link TypeResolverBuilder} * implementation. */ protected StdTypeResolverBuilder _constructStdTypeResolverBuilder() { return new StdTypeResolverBuilder(); } /** * Helper method for constructing standard {@link TypeResolverBuilder} * implementation. * * @since 2.16 (backported from Jackson 3.0) */ protected TypeResolverBuilder _constructStdTypeResolverBuilder(MapperConfig config, JsonTypeInfo.Value typeInfo, JavaType baseType) { return new StdTypeResolverBuilder(typeInfo); } /** * Helper method for dealing with "no type info" marker; can't be null * (as it'd be replaced by default typing) */ protected StdTypeResolverBuilder _constructNoTypeResolverBuilder() { return StdTypeResolverBuilder.noTypeInfoBuilder(); } private boolean _primitiveAndWrapper(Class baseType, Class refinement) { if (baseType.isPrimitive()) { return baseType == ClassUtil.primitiveType(refinement); } if (refinement.isPrimitive()) { return refinement == ClassUtil.primitiveType(baseType); } return false; } private boolean _primitiveAndWrapper(JavaType baseType, Class refinement) { if (baseType.isPrimitive()) { return baseType.hasRawClass(ClassUtil.primitiveType(refinement)); } if (refinement.isPrimitive()) { return refinement == ClassUtil.primitiveType(baseType.getRawClass()); } return false; } // @since 2.12 private JsonMappingException _databindException(String msg) { return new JsonMappingException(null, msg); } // @since 2.12 private JsonMappingException _databindException(Throwable t, String msg) { return new JsonMappingException(null, msg, t); } }