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

io.honeybadger.com.fasterxml.jackson.databind.ser.PropertyBuilder Maven / Gradle / Ivy

There is a newer version: 2.1.2
Show newest version
package com.fasterxml.jackson.databind.ser;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.introspect.*;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.util.*;

/**
 * Helper class for {@link BeanSerializerFactory} that is used to
 * construct {@link BeanPropertyWriter} instances. Can be sub-classed
 * to change behavior.
 */
public class PropertyBuilder
{
    // @since 2.7
    private final static Object NO_DEFAULT_MARKER = Boolean.FALSE;

    final protected SerializationConfig _config;
    final protected BeanDescription _beanDesc;

    final protected AnnotationIntrospector _annotationIntrospector;

    /**
     * If a property has serialization inclusion value of
     * {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_DEFAULT},
     * we may need to know the default value of the bean, to know if property value
     * equals default one.
     *

* NOTE: only used if enclosing class defines NON_DEFAULT, but NOT if it is the * global default OR per-property override. */ protected Object _defaultBean; /** * Default inclusion mode for properties of the POJO for which * properties are collected; possibly overridden on * per-property basis. Combines global inclusion defaults and * per-type (annotation and type-override) inclusion overrides. */ final protected JsonInclude.Value _defaultInclusion; /** * Marker flag used to indicate that "real" default values are to be used * for properties, as per per-type value inclusion of type NON_DEFAULT * * @since 2.8 */ final protected boolean _useRealPropertyDefaults; public PropertyBuilder(SerializationConfig config, BeanDescription beanDesc) { _config = config; _beanDesc = beanDesc; // 08-Sep-2016, tatu: This gets tricky, with 3 levels of definitions: // (a) global default inclusion // (b) per-type default inclusion (from annotation or config overrides; // latter having precedence // Cc) per-property override // // and not only requiring merging, but also considering special handling // for NON_DEFAULT in case of (b) (vs (a) or (c)) JsonInclude.Value inclPerType = JsonInclude.Value.merge( beanDesc.findPropertyInclusion(JsonInclude.Value.empty()), config.getDefaultPropertyInclusion(beanDesc.getBeanClass(), JsonInclude.Value.empty())); _defaultInclusion = JsonInclude.Value.merge(config.getDefaultPropertyInclusion(), inclPerType); _useRealPropertyDefaults = inclPerType.getValueInclusion() == JsonInclude.Include.NON_DEFAULT; _annotationIntrospector = _config.getAnnotationIntrospector(); } /* /********************************************************** /* Public API /********************************************************** */ public Annotations getClassAnnotations() { return _beanDesc.getClassAnnotations(); } /** * @param contentTypeSer Optional explicit type information serializer * to use for contained values (only used for properties that are * of container type) */ @SuppressWarnings("deprecation") protected BeanPropertyWriter buildWriter(SerializerProvider prov, BeanPropertyDefinition propDef, JavaType declaredType, JsonSerializer ser, TypeSerializer typeSer, TypeSerializer contentTypeSer, AnnotatedMember am, boolean defaultUseStaticTyping) throws JsonMappingException { // do we have annotation that forces type to use (to declared type or its super type)? JavaType serializationType; try { serializationType = findSerializationType(am, defaultUseStaticTyping, declaredType); } catch (JsonMappingException e) { return prov.reportBadPropertyDefinition(_beanDesc, propDef, e.getMessage()); } // Container types can have separate type serializers for content (value / element) type if (contentTypeSer != null) { /* 04-Feb-2010, tatu: Let's force static typing for collection, if there is * type information for contents. Should work well (for JAXB case); can be * revisited if this causes problems. */ if (serializationType == null) { // serializationType = TypeFactory.type(am.getGenericType(), _beanDesc.getType()); serializationType = declaredType; } JavaType ct = serializationType.getContentType(); // Not exactly sure why, but this used to occur; better check explicitly: if (ct == null) { prov.reportBadPropertyDefinition(_beanDesc, propDef, "serialization type "+serializationType+" has no content"); } serializationType = serializationType.withContentTypeHandler(contentTypeSer); ct = serializationType.getContentType(); } Object valueToSuppress = null; boolean suppressNulls = false; // 12-Jul-2016, tatu: [databind#1256] Need to make sure we consider type refinement JavaType actualType = (serializationType == null) ? declaredType : serializationType; // 17-Aug-2016, tatu: Default inclusion covers global default (for all types), as well // as type-default for enclosing POJO. What we need, then, is per-type default (if any) // for declared property type... and finally property annotation overrides JsonInclude.Value inclV = _config.getDefaultPropertyInclusion(actualType.getRawClass(), _defaultInclusion); // property annotation override inclV = inclV.withOverrides(propDef.findInclusion()); JsonInclude.Include inclusion = inclV.getValueInclusion(); if (inclusion == JsonInclude.Include.USE_DEFAULTS) { // should not occur but... inclusion = JsonInclude.Include.ALWAYS; } switch (inclusion) { case NON_DEFAULT: // 11-Nov-2015, tatu: This is tricky because semantics differ between cases, // so that if enclosing class has this, we may need to access values of property, // whereas for global defaults OR per-property overrides, we have more // static definition. Sigh. // First: case of class/type specifying it; try to find POJO property defaults Object defaultBean; // 16-Oct-2016, tatu: Note: if we can not for some reason create "default instance", // revert logic to the case of general/per-property handling, so both // type-default AND null are to be excluded. // (as per [databind#1417] if (_useRealPropertyDefaults && (defaultBean = getDefaultBean()) != null) { // 07-Sep-2016, tatu: may also need to front-load access forcing now if (prov.isEnabled(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)) { am.fixAccess(_config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS)); } try { valueToSuppress = am.getValue(defaultBean); } catch (Exception e) { _throwWrapped(e, propDef.getName(), defaultBean); } } else { valueToSuppress = getDefaultValue(actualType); suppressNulls = true; } if (valueToSuppress == null) { suppressNulls = true; } else { if (valueToSuppress.getClass().isArray()) { valueToSuppress = ArrayBuilders.getArrayComparator(valueToSuppress); } } break; case NON_ABSENT: // new with 2.6, to support Guava/JDK8 Optionals // always suppress nulls suppressNulls = true; // and for referential types, also "empty", which in their case means "absent" if (actualType.isReferenceType()) { valueToSuppress = BeanPropertyWriter.MARKER_FOR_EMPTY; } break; case NON_EMPTY: // always suppress nulls suppressNulls = true; // but possibly also 'empty' values: valueToSuppress = BeanPropertyWriter.MARKER_FOR_EMPTY; break; case NON_NULL: suppressNulls = true; // fall through case ALWAYS: // default default: // we may still want to suppress empty collections, as per [JACKSON-254]: if (actualType.isContainerType() && !_config.isEnabled(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS)) { valueToSuppress = BeanPropertyWriter.MARKER_FOR_EMPTY; } break; } BeanPropertyWriter bpw = new BeanPropertyWriter(propDef, am, _beanDesc.getClassAnnotations(), declaredType, ser, typeSer, serializationType, suppressNulls, valueToSuppress); // How about custom null serializer? Object serDef = _annotationIntrospector.findNullSerializer(am); if (serDef != null) { bpw.assignNullSerializer(prov.serializerInstance(am, serDef)); } // And then, handling of unwrapping NameTransformer unwrapper = _annotationIntrospector.findUnwrappingNameTransformer(am); if (unwrapper != null) { bpw = bpw.unwrappingWriter(unwrapper); } return bpw; } /* /********************************************************** /* Helper methods; annotation access /********************************************************** */ /** * Method that will try to determine statically defined type of property * being serialized, based on annotations (for overrides), and alternatively * declared type (if static typing for serialization is enabled). * If neither can be used (no annotations, dynamic typing), returns null. */ protected JavaType findSerializationType(Annotated a, boolean useStaticTyping, JavaType declaredType) throws JsonMappingException { JavaType secondary = _annotationIntrospector.refineSerializationType(_config, a, declaredType); // 11-Oct-2015, tatu: As of 2.7, not 100% sure following checks are needed. But keeping // for now, just in case if (secondary != declaredType) { Class serClass = secondary.getRawClass(); // Must be a super type to be usable Class rawDeclared = declaredType.getRawClass(); if (serClass.isAssignableFrom(rawDeclared)) { ; // fine as is } else { /* 18-Nov-2010, tatu: Related to fixing [JACKSON-416], an issue with such * check is that for deserialization more specific type makes sense; * and for serialization more generic. But alas JAXB uses but a single * annotation to do both... Hence, we must just discard type, as long as * types are related */ if (!rawDeclared.isAssignableFrom(serClass)) { throw new IllegalArgumentException("Illegal concrete-type annotation for method '"+a.getName()+"': class "+serClass.getName()+" not a super-type of (declared) class "+rawDeclared.getName()); } /* 03-Dec-2010, tatu: Actually, ugh, we may need to further relax this * and actually accept subtypes too for serialization. Bit dangerous in theory * but need to trust user here... */ } useStaticTyping = true; declaredType = secondary; } // If using static typing, declared type is known to be the type... JsonSerialize.Typing typing = _annotationIntrospector.findSerializationTyping(a); if ((typing != null) && (typing != JsonSerialize.Typing.DEFAULT_TYPING)) { useStaticTyping = (typing == JsonSerialize.Typing.STATIC); } if (useStaticTyping) { // 11-Oct-2015, tatu: Make sure JavaType also "knows" static-ness... return declaredType.withStaticTyping(); } return null; } /* /********************************************************** /* Helper methods for default value handling /********************************************************** */ protected Object getDefaultBean() { Object def = _defaultBean; if (def == null) { /* If we can fix access rights, we should; otherwise non-public * classes or default constructor will prevent instantiation */ def = _beanDesc.instantiateBean(_config.canOverrideAccessModifiers()); if (def == null) { // 06-Nov-2015, tatu: As per [databind#998], do not fail. /* Class cls = _beanDesc.getClassInfo().getAnnotated(); throw new IllegalArgumentException("Class "+cls.getName()+" has no default constructor; can not instantiate default bean value to support 'properties=JsonSerialize.Inclusion.NON_DEFAULT' annotation"); */ // And use a marker def = NO_DEFAULT_MARKER; } _defaultBean = def; } return (def == NO_DEFAULT_MARKER) ? null : _defaultBean; } /** * Accessor used to find out "default value" for given property, to use for * comparing values to serialize, to determine whether to exclude value from serialization with * inclusion type of {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_DEFAULT}. * This method is called when we specifically want to know default value within context * of a POJO, when annotation is within containing class, and not for property or * defined as global baseline. *

* Note that returning of pseudo-type * {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_EMPTY} requires special handling. * * @since 2.7 * @deprecated Since 2.8.5 since this will not allow determining difference between "no default instance" * case and default being `null`. */ @Deprecated // since 2.8.5 protected Object getPropertyDefaultValue(String name, AnnotatedMember member, JavaType type) { Object defaultBean = getDefaultBean(); if (defaultBean == null) { return getDefaultValue(type); } try { return member.getValue(defaultBean); } catch (Exception e) { return _throwWrapped(e, name, defaultBean); } } /** * Accessor used to find out "default value" to use for comparing values to * serialize, to determine whether to exclude value from serialization with * inclusion type of {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_DEFAULT}. *

* Default logic is such that for primitives and wrapper types for primitives, expected * defaults (0 for `int` and `java.lang.Integer`) are returned; for Strings, empty String, * and for structured (Maps, Collections, arrays) and reference types, criteria * {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_DEFAULT} * is used. * * @since 2.7 */ protected Object getDefaultValue(JavaType type) { // 06-Nov-2015, tatu: Returning null is fine for Object types; but need special // handling for primitives since they are never passed as nulls. Class cls = type.getRawClass(); Class prim = ClassUtil.primitiveType(cls); if (prim != null) { return ClassUtil.defaultValue(prim); } if (type.isContainerType() || type.isReferenceType()) { return JsonInclude.Include.NON_EMPTY; } if (cls == String.class) { return ""; } return null; } /* /********************************************************** /* Helper methods for exception handling /********************************************************** */ protected Object _throwWrapped(Exception e, String propName, Object defaultBean) { Throwable t = e; while (t.getCause() != null) { t = t.getCause(); } if (t instanceof Error) throw (Error) t; if (t instanceof RuntimeException) throw (RuntimeException) t; throw new IllegalArgumentException("Failed to get property '"+propName+"' of default "+defaultBean.getClass().getName()+" instance"); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy