JsonValueSerializer.java
package tools.jackson.databind.ser.jackson;
import java.lang.reflect.InvocationTargetException;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.UnaryOperator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import tools.jackson.core.*;
import tools.jackson.core.type.WritableTypeId;
import tools.jackson.databind.*;
import tools.jackson.databind.annotation.JacksonStdImpl;
import tools.jackson.databind.introspect.AnnotatedMember;
import tools.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
import tools.jackson.databind.jsonFormatVisitors.JsonStringFormatVisitor;
import tools.jackson.databind.jsontype.TypeIdResolver;
import tools.jackson.databind.jsontype.TypeSerializer;
import tools.jackson.databind.ser.BeanSerializer;
import tools.jackson.databind.ser.std.StdDynamicSerializer;
import tools.jackson.databind.util.ClassUtil;
/**
* Serializer class that can serialize Object that have a
* {@link com.fasterxml.jackson.annotation.JsonValue} annotation to
* indicate that serialization should be done by calling the method
* annotated, and serializing result it returns.
*<p>
* Implementation note: we will post-process resulting serializer
* (much like what is done with {@link BeanSerializer})
* to figure out actual serializers for final types.
* This must be done from {@link #createContextual} method, and NOT from constructor;
* otherwise we could end up with an infinite loop.
*/
@JacksonStdImpl
public class JsonValueSerializer
extends StdDynamicSerializer<Object>
{
/**
* Accessor (field, getter) used to access value to serialize.
*/
protected final AnnotatedMember _accessor;
/**
* Value for annotated accessor.
*/
protected final JavaType _valueType;
protected final boolean _staticTyping;
/**
* This is a flag that is set in rare (?) cases where this serializer
* is used for "natural" types (boolean, int, String, double); and where
* we actually must force type information wrapping, even though
* one would not normally be added.
*/
protected final boolean _forceTypeInformation;
protected final Set<String> _ignoredProperties;
/*
/**********************************************************************
/* Life-cycle
/**********************************************************************
*/
/**
* @param ser Explicit serializer to use, if caller knows it (which
* occurs if and only if the "value method" was annotated with
* {@link tools.jackson.databind.annotation.JsonSerialize#using}), otherwise
* null
*/
protected JsonValueSerializer(JavaType nominalType,
JavaType valueType, boolean staticTyping,
TypeSerializer vts, ValueSerializer<?> ser,
AnnotatedMember accessor,
Set<String> ignoredProperties)
{
super(nominalType, null, vts, ser);
_valueType = valueType;
_staticTyping = staticTyping;
_accessor = accessor;
_forceTypeInformation = true; // gets reconsidered when we are contextualized
_ignoredProperties = ignoredProperties;
}
protected JsonValueSerializer(JsonValueSerializer src, BeanProperty property,
TypeSerializer vts, ValueSerializer<?> ser, boolean forceTypeInfo)
{
super(src, property, vts, ser);
_valueType = src._valueType;
_accessor = src._accessor;
_staticTyping = src._staticTyping;
_forceTypeInformation = forceTypeInfo;
_ignoredProperties = src._ignoredProperties;
}
public static JsonValueSerializer construct(SerializationConfig config,
JavaType nominalType,
JavaType valueType, boolean staticTyping,
TypeSerializer vts, ValueSerializer<?> ser,
AnnotatedMember accessor)
{
JsonIgnoreProperties.Value ignorals = config.getAnnotationIntrospector()
.findPropertyIgnoralByName(config, accessor);
final Set<String> ignoredProperties = ignorals.findIgnoredForSerialization();
ser = _withIgnoreProperties(ser, ignoredProperties);
return new JsonValueSerializer(nominalType, valueType, staticTyping,
vts, ser, accessor, ignoredProperties);
}
public JsonValueSerializer withResolved(BeanProperty property,
TypeSerializer vts, ValueSerializer<?> ser, boolean forceTypeInfo)
{
if ((_property == property)
&& (_valueTypeSerializer == vts) && (_valueSerializer == ser)
&& (forceTypeInfo == _forceTypeInformation)) {
return this;
}
return new JsonValueSerializer(this, property, vts, ser, forceTypeInfo);
}
/*
/**********************************************************
/* Overrides
/**********************************************************
*/
@Override // since 2.12
public boolean isEmpty(SerializationContext ctxt, Object bean)
{
// 31-Oct-2020, tatu: Should perhaps catch access issue here... ?
Object referenced = _accessor.getValue(bean);
if (referenced == null) {
return true;
}
ValueSerializer<Object> ser = _valueSerializer;
if (ser == null) {
ser = _findSerializer(ctxt, referenced);
}
return ser.isEmpty(ctxt, referenced);
}
/*
/**********************************************************************
/* Post-processing
/**********************************************************************
*/
/**
* We can try to find the actual serializer for value, if we can
* statically figure out what the result type must be.
*/
@Override
public ValueSerializer<?> createContextual(SerializationContext ctxt,
BeanProperty property)
{
TypeSerializer vts = _valueTypeSerializer;
if (vts != null) {
vts = vts.forProperty(ctxt, property);
}
ValueSerializer<?> ser = _valueSerializer;
if (ser == null) {
// Can only assign serializer statically if the declared type is final:
// if not, we don't really know the actual type until we get the instance.
// 10-Mar-2010, tatu: Except if static typing is to be used
if (_staticTyping || ctxt.isEnabled(MapperFeature.USE_STATIC_TYPING)
|| _valueType.isFinal()) {
// false -> no need to cache
/* 10-Mar-2010, tatu: Ideally we would actually separate out type
* serializer from value serializer; but, alas, there's no access
* to serializer factory at this point...
*/
// I _think_ this can be considered a primary property...
ser = ctxt.findPrimaryPropertySerializer(_valueType, property);
ser = _withIgnoreProperties(ser, _ignoredProperties);
/* 09-Dec-2010, tatu: Turns out we must add special handling for
* cases where "native" (aka "natural") type is being serialized,
* using standard serializer
*/
boolean forceTypeInformation = isNaturalTypeWithStdHandling(_valueType.getRawClass(), ser);
return withResolved(property, vts, ser, forceTypeInformation);
}
// [databind#2822]: better hold on to "property", regardless
if (property != _property) {
return withResolved(property, vts, ser, _forceTypeInformation);
}
} else {
// 05-Sep-2013, tatu: I _think_ this can be considered a primary property...
ser = ctxt.handlePrimaryContextualization(ser, property);
return withResolved(property, vts, ser, _forceTypeInformation);
}
return this;
}
/*
/**********************************************************************
/* Actual serialization
/**********************************************************************
*/
@Override
public void serialize(final Object bean, final JsonGenerator gen,
final SerializationContext ctxt)
throws JacksonException
{
Object value;
try {
value = _accessor.getValue(bean);
} catch (Exception e) {
wrapAndThrow(ctxt, e, bean, _accessor.getName() + "()");
return; // never gets here
}
if (value == null) {
ctxt.defaultSerializeNullValue(gen);
return;
}
ValueSerializer<Object> ser = _valueSerializer;
if (ser == null) {
ser = _findSerializer(ctxt, value);
}
if (_valueTypeSerializer != null) {
ser.serializeWithType(value, gen, ctxt, _valueTypeSerializer);
} else {
ser.serialize(value, gen, ctxt);
}
}
protected ValueSerializer<Object> _findSerializer(SerializationContext ctxt, Object value) {
final UnaryOperator<ValueSerializer<Object>> serTransformer =
valueSer -> _withIgnoreProperties(valueSer, _ignoredProperties);
Class<?> cc = value.getClass();
if (_valueType.hasGenericTypes()) {
return _findAndAddDynamic(ctxt,
ctxt.constructSpecializedType(_valueType, cc),
serTransformer);
}
return _findAndAddDynamic(ctxt, cc, serTransformer);
}
/**
* Internal helper that configures the provided {@code ser} to ignore properties specified by {@link JsonIgnoreProperties}.
*
* @param ser Serializer to be configured
* @param ignoredProperties Properties to ignore, if any
*
* @return Configured serializer with specified properties ignored
*/
@SuppressWarnings("unchecked")
protected static ValueSerializer<Object> _withIgnoreProperties(ValueSerializer<?> ser,
Set<String> ignoredProperties)
{
if (ser != null) {
if (!ignoredProperties.isEmpty()) {
ser = ser.withIgnoredProperties(ignoredProperties);
}
}
return (ValueSerializer<Object>) ser;
}
@Override
public void serializeWithType(Object bean, JsonGenerator gen, SerializationContext ctxt,
TypeSerializer typeSer0) throws JacksonException
{
// Regardless of other parts, first need to find value to serialize:
Object value;
try {
value = _accessor.getValue(bean);
} catch (Exception e) {
wrapAndThrow(ctxt, e, bean, _accessor.getName() + "()");
return; // never gets here
}
// and if we got null, can also just write it directly
if (value == null) {
ctxt.defaultSerializeNullValue(gen);
return;
}
ValueSerializer<Object> ser = _valueSerializer;
if (ser == null) {
Class<?> cc = value.getClass();
if (_valueType.hasGenericTypes()) {
ser = _findAndAddDynamic(ctxt, ctxt.constructSpecializedType(_valueType, cc));
} else {
ser = _findAndAddDynamic(ctxt, cc);
}
}
// 16-Apr-2018, tatu: This is interesting piece of vestigal code but...
// I guess it is still needed, too.
// 09-Dec-2010, tatu: To work around natural type's refusal to add type info, we do
// this (note: type is for the wrapper type, not enclosed value!)
if (_forceTypeInformation) {
// Confusing? Type id is for POJO and NOT for value returned by JsonValue accessor...
WritableTypeId typeIdDef = typeSer0.writeTypePrefix(gen, ctxt,
typeSer0.typeId(bean, JsonToken.VALUE_STRING));
ser.serialize(value, gen, ctxt);
typeSer0.writeTypeSuffix(gen, ctxt, typeIdDef);
return;
}
// 28-Sep-2016, tatu: As per [databind#1385], we do need to do some juggling
// to use different Object for type id (logical type) and actual serialization
// (delegate type).
// 16-Apr-2018, tatu: What seems suspicious is that we do not use `_valueTypeSerializer`
// for anything but... it appears to work wrt existing tests, and alternative
// is not very clear. So most likely it'll fail at some point and require
// full investigation. But not today.
TypeSerializerRerouter rr = new TypeSerializerRerouter(typeSer0, bean);
ser.serializeWithType(value, gen, ctxt, rr);
}
@Override
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
{
/* 27-Apr-2015, tatu: First things first; for JSON Schema introspection,
* Enum types that use `@JsonValue` are special (but NOT necessarily
* anything else that RETURNS an enum!)
* So we will need to add special
* handling here (see https://github.com/FasterXML/jackson-module-jsonSchema/issues/57
* for details).
*
* Note that meaning of JsonValue, then, is very different for Enums. Sigh.
*/
final JavaType type = _accessor.getType();
Class<?> declaring = _accessor.getDeclaringClass();
if ((declaring != null) && ClassUtil.isEnumType(declaring)) {
if (_acceptJsonFormatVisitorForEnum(visitor, typeHint, declaring)) {
return;
}
}
ValueSerializer<Object> ser = _valueSerializer;
if (ser == null) {
ser = visitor.getContext().findPrimaryPropertySerializer(type, _property);
if (ser == null) { // can this ever occur?
visitor.expectAnyFormat(typeHint);
return;
}
}
ser.acceptJsonFormatVisitor(visitor, type);
}
/**
* Overridable helper method used for special case handling of schema information for
* Enums.
*
* @return True if method handled callbacks; false if not; in latter case caller will
* send default callbacks
*/
protected boolean _acceptJsonFormatVisitorForEnum(JsonFormatVisitorWrapper visitor,
JavaType typeHint, Class<?> enumType)
{
// Copied from EnumSerializer#acceptJsonFormatVisitor
JsonStringFormatVisitor stringVisitor = visitor.expectStringFormat(typeHint);
if (stringVisitor != null) {
Set<String> enums = new LinkedHashSet<String>();
for (Object en : enumType.getEnumConstants()) {
try {
// 21-Apr-2016, tatu: This is convoluted to the max, but essentially we
// call `@JsonValue`-annotated accessor method on all Enum members,
// so it all "works out". To some degree.
enums.add(String.valueOf(_accessor.getValue(en)));
} catch (Exception e) {
Throwable t = e;
while (t instanceof InvocationTargetException && t.getCause() != null) {
t = t.getCause();
}
ClassUtil.throwIfError(t);
throw DatabindException.wrapWithPath(visitor.getContext(), t,
new JacksonException.Reference(en, _accessor.getName() + "()"));
}
}
stringVisitor.enumTypes(enums);
}
return true;
}
protected boolean isNaturalTypeWithStdHandling(Class<?> rawType, ValueSerializer<?> ser)
{
// First: do we have a natural type being handled?
if (rawType.isPrimitive()) {
if (rawType != Integer.TYPE && rawType != Boolean.TYPE && rawType != Double.TYPE) {
return false;
}
} else {
if (rawType != String.class &&
rawType != Integer.class && rawType != Boolean.class && rawType != Double.class) {
return false;
}
}
return isDefaultSerializer(ser);
}
/*
/**********************************************************************
/* Helper class(es)
/**********************************************************************
*/
/**
* Silly little wrapper class we need to re-route type serialization so that we can
* override Object to use for type id (logical type) even when asking serialization
* of something else (delegate type)
*/
static class TypeSerializerRerouter
extends TypeSerializer
{
protected final TypeSerializer _typeSerializer;
protected final Object _forObject;
public TypeSerializerRerouter(TypeSerializer ts, Object ob) {
_typeSerializer = ts;
_forObject = ob;
}
@Override
public TypeSerializer forProperty(SerializationContext ctxt,
BeanProperty prop) { // should never get called
throw new UnsupportedOperationException();
}
@Override
public As getTypeInclusion() {
return _typeSerializer.getTypeInclusion();
}
@Override
public String getPropertyName() {
return _typeSerializer.getPropertyName();
}
@Override
public TypeIdResolver getTypeIdResolver() {
return _typeSerializer.getTypeIdResolver();
}
@Override
public WritableTypeId writeTypePrefix(JsonGenerator g, SerializationContext ctxt,
WritableTypeId typeId) throws JacksonException
{
// 28-Jun-2017, tatu: Important! Need to "override" value
typeId.forValue = _forObject;
return _typeSerializer.writeTypePrefix(g, ctxt, typeId);
}
@Override
public WritableTypeId writeTypeSuffix(JsonGenerator g, SerializationContext ctxt,
WritableTypeId typeId) throws JacksonException
{
// NOTE: already overwrote value object so:
return _typeSerializer.writeTypeSuffix(g, ctxt, typeId);
}
}
}