com.fasterxml.jackson.databind.ser.std.EnumSerializer Maven / Gradle / Ivy
package com.fasterxml.jackson.databind.ser.std;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonFormat.Shape;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.introspect.AnnotatedClass;
import com.fasterxml.jackson.databind.introspect.EnumNamingStrategyFactory;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonStringFormatVisitor;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.fasterxml.jackson.databind.util.EnumValues;
/**
* Standard serializer used for {@link java.lang.Enum} types.
*
* Based on {@link StdScalarSerializer} since the JSON value is
* scalar (String).
*/
@JacksonStdImpl
public class EnumSerializer
extends StdScalarSerializer>
implements ContextualSerializer
{
private static final long serialVersionUID = 1L;
/**
* This map contains pre-resolved values (since there are ways
* to customize actual String constants to use) to use as
* serializations.
*/
protected final EnumValues _values;
/**
* Flag that is set if we statically know serialization choice
* between index and textual format (null if it needs to be dynamically
* checked).
*
* @since 2.1
*/
protected final Boolean _serializeAsIndex;
/**
* Map with key as converted property class defined implementation of {@link EnumNamingStrategy}
* and with value as Enum names collected using Enum.name()
.
*
* @since 2.15
*/
protected final EnumValues _valuesByEnumNaming;
/**
* Map that contains pre-resolved values for {@link Enum#toString} to use for serialization,
* while respecting {@link com.fasterxml.jackson.annotation.JsonProperty}
* and {@link com.fasterxml.jackson.databind.cfg.EnumFeature#WRITE_ENUMS_TO_LOWERCASE}.
*
* @since 2.16
*/
protected final EnumValues _valuesByToString;
/*
/**********************************************************
/* Construction, initialization
/**********************************************************
*/
/**
* @deprecated Since 2.16
*/
@Deprecated // since 2.16
public EnumSerializer(EnumValues v, Boolean serializeAsIndex)
{
this(v, serializeAsIndex, null, null);
}
/**
* @since 2.15
* @deprecated Since 2.16
*/
@Deprecated
public EnumSerializer(EnumValues v, Boolean serializeAsIndex, EnumValues valuesByEnumNaming)
{
this(v, serializeAsIndex, valuesByEnumNaming, null);
}
/**
* @since 2.16
*/
public EnumSerializer(EnumValues v, Boolean serializeAsIndex, EnumValues valuesByEnumNaming,
EnumValues valuesByToString)
{
super(v.getEnumClass(), false);
_values = v;
_serializeAsIndex = serializeAsIndex;
_valuesByEnumNaming = valuesByEnumNaming;
_valuesByToString = valuesByToString;
}
/**
* Factory method used by {@link com.fasterxml.jackson.databind.ser.BasicSerializerFactory}
* for constructing serializer instance of Enum types.
*
* @since 2.1
*/
@SuppressWarnings("unchecked")
public static EnumSerializer construct(Class> enumClass, SerializationConfig config,
BeanDescription beanDesc, JsonFormat.Value format)
{
/* 08-Apr-2015, tatu: As per [databind#749], we cannot statically determine
* between name() and toString(), need to construct `EnumValues` with names,
* handle toString() case dynamically (for example)
*/
EnumValues v = EnumValues.constructFromName(config, beanDesc.getClassInfo());
EnumValues valuesByEnumNaming = constructEnumNamingStrategyValues(config, (Class>) enumClass, beanDesc.getClassInfo());
EnumValues valuesByToString = EnumValues.constructFromToString(config, beanDesc.getClassInfo());
Boolean serializeAsIndex = _isShapeWrittenUsingIndex(enumClass, format, true, null);
return new EnumSerializer(v, serializeAsIndex, valuesByEnumNaming, valuesByToString);
}
/**
* To support some level of per-property configuration, we will need
* to make things contextual. We are limited to "textual vs index"
* choice here, however.
*/
@Override
public JsonSerializer> createContextual(SerializerProvider serializers,
BeanProperty property) throws JsonMappingException
{
JsonFormat.Value format = findFormatOverrides(serializers,
property, handledType());
if (format != null) {
Class> type = handledType();
Boolean serializeAsIndex = _isShapeWrittenUsingIndex(type,
format, false, _serializeAsIndex);
if (!Objects.equals(serializeAsIndex, _serializeAsIndex)) {
return new EnumSerializer(_values, serializeAsIndex,
_valuesByEnumNaming, _valuesByToString);
}
}
return this;
}
/*
/**********************************************************
/* Extended API for Jackson databind core
/**********************************************************
*/
public EnumValues getEnumValues() { return _values; }
/*
/**********************************************************
/* Actual serialization
/**********************************************************
*/
@Override
public final void serialize(Enum> en, JsonGenerator gen, SerializerProvider serializers)
throws IOException
{
if (_valuesByEnumNaming != null) {
gen.writeString(_valuesByEnumNaming.serializedValueFor(en));
return;
}
if (_serializeAsIndex(serializers)) {
gen.writeNumber(en.ordinal());
return;
}
// [databind#749]: or via toString()?
if (serializers.isEnabled(SerializationFeature.WRITE_ENUMS_USING_TO_STRING)) {
gen.writeString(_valuesByToString.serializedValueFor(en));
return;
}
gen.writeString(_values.serializedValueFor(en));
}
/*
/**********************************************************
/* Schema support
/**********************************************************
*/
/**
* @deprecated Since 2.15
*/
@Deprecated
@Override
public JsonNode getSchema(SerializerProvider provider, Type typeHint)
{
if (_serializeAsIndex(provider)) {
return createSchemaNode("integer", true);
}
ObjectNode objectNode = createSchemaNode("string", true);
if (typeHint != null) {
JavaType type = provider.constructType(typeHint);
if (type.isEnumType()) {
ArrayNode enumNode = objectNode.putArray("enum");
for (SerializableString value : _values.values()) {
enumNode.add(value.getValue());
}
}
}
return objectNode;
}
@Override
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
throws JsonMappingException
{
SerializerProvider serializers = visitor.getProvider();
if (_serializeAsIndex(serializers)) {
visitIntFormat(visitor, typeHint, JsonParser.NumberType.INT);
return;
}
JsonStringFormatVisitor stringVisitor = visitor.expectStringFormat(typeHint);
if (stringVisitor != null) {
Set enums = new LinkedHashSet();
// Use toString()?
if ((serializers != null) &&
serializers.isEnabled(SerializationFeature.WRITE_ENUMS_USING_TO_STRING)) {
for (SerializableString value : _valuesByToString.values()) {
enums.add(value.getValue());
}
} else {
// No, serialize using name() or explicit overrides
for (SerializableString value : _values.values()) {
enums.add(value.getValue());
}
}
stringVisitor.enumTypes(enums);
}
}
/*
/**********************************************************
/* Helper methods
/**********************************************************
*/
protected final boolean _serializeAsIndex(SerializerProvider serializers)
{
if (_serializeAsIndex != null) {
return _serializeAsIndex;
}
return serializers.isEnabled(SerializationFeature.WRITE_ENUMS_USING_INDEX);
}
/**
* Helper method called to check whether serialization should be done using
* index (number) or not.
*/
protected static Boolean _isShapeWrittenUsingIndex(Class> enumClass,
JsonFormat.Value format, boolean fromClass,
Boolean defaultValue)
{
JsonFormat.Shape shape = (format == null) ? null : format.getShape();
if (shape == null) {
return defaultValue;
}
// i.e. "default", check dynamically
if (shape == Shape.ANY || shape == Shape.SCALAR) {
return defaultValue;
}
// 19-May-2016, tatu: also consider "natural" shape
if (shape == Shape.STRING || shape == Shape.NATURAL) {
return Boolean.FALSE;
}
// 01-Oct-2014, tatu: For convenience, consider "as-array" to also mean 'yes, use index')
if (shape.isNumeric() || (shape == Shape.ARRAY)) {
return Boolean.TRUE;
}
// 07-Mar-2017, tatu: Also means `OBJECT` not available as property annotation...
throw new IllegalArgumentException(String.format(
"Unsupported serialization shape (%s) for Enum %s, not supported as %s annotation",
shape, enumClass.getName(), (fromClass? "class" : "property")));
}
/**
* Factory method used to resolve an instance of {@link EnumValues}
* with {@link EnumNamingStrategy} applied for the target class.
*
* @since 2.15
*/
protected static EnumValues constructEnumNamingStrategyValues(SerializationConfig config, Class> enumClass,
AnnotatedClass annotatedClass) {
Object namingDef = config.getAnnotationIntrospector().findEnumNamingStrategy(config,
annotatedClass);
EnumNamingStrategy enumNamingStrategy = EnumNamingStrategyFactory.createEnumNamingStrategyInstance(
namingDef, config.canOverrideAccessModifiers());
return enumNamingStrategy == null ? null : EnumValues.constructUsingEnumNamingStrategy(
config, annotatedClass, enumNamingStrategy);
}
}