EnumSetDeserializer.java
package tools.jackson.databind.deser.jdk;
import java.util.*;
import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonFormat;
import tools.jackson.core.*;
import tools.jackson.databind.*;
import tools.jackson.databind.deser.NullValueProvider;
import tools.jackson.databind.deser.impl.NullsConstantProvider;
import tools.jackson.databind.deser.std.StdDeserializer;
import tools.jackson.databind.exc.InvalidNullException;
import tools.jackson.databind.jsontype.TypeDeserializer;
import tools.jackson.databind.type.LogicalType;
import tools.jackson.databind.util.AccessPattern;
/**
* Standard deserializer for {@link EnumSet}s.
* <p>
* Note: casting within this class is all messed up -- just could not figure out a way
* to properly deal with recursive definition of "EnumSet<K extends Enum<K>, V>
*/
@SuppressWarnings("rawtypes")
public class EnumSetDeserializer
extends StdDeserializer<EnumSet<?>>
{
protected final JavaType _enumType;
protected ValueDeserializer<Enum<?>> _enumDeserializer;
/**
* Handler we need for dealing with nulls.
*/
protected final NullValueProvider _nullProvider;
/**
* Marker flag set if the <code>_nullProvider</code> indicates that all null
* content values should be skipped (instead of being possibly converted).
*/
protected final boolean _skipNullValues;
/**
* Specific override for this instance (from proper, or global per-type overrides)
* to indicate whether single value may be taken to mean an unwrapped one-element array
* or not. If null, left to global defaults.
*/
protected final Boolean _unwrapSingle;
/*
/**********************************************************************
/* Life-cycle
/**********************************************************************
*/
@SuppressWarnings("unchecked")
public EnumSetDeserializer(JavaType enumType, ValueDeserializer<?> deser)
{
super(EnumSet.class);
_enumType = enumType;
// sanity check
if (!enumType.isEnumType()) {
throw new IllegalArgumentException("Type "+enumType+" not Java Enum type");
}
_enumDeserializer = (ValueDeserializer<Enum<?>>) deser;
_unwrapSingle = null;
_nullProvider = null;
_skipNullValues = false;
}
@SuppressWarnings("unchecked" )
protected EnumSetDeserializer(EnumSetDeserializer base,
ValueDeserializer<?> deser,
NullValueProvider nuller, Boolean unwrapSingle) {
super(base);
_enumType = base._enumType;
_enumDeserializer = (ValueDeserializer<Enum<?>>) deser;
_nullProvider = nuller;
_skipNullValues = NullsConstantProvider.isSkipper(nuller);
_unwrapSingle = unwrapSingle;
}
public EnumSetDeserializer withDeserializer(ValueDeserializer<?> deser) {
if (_enumDeserializer == deser) {
return this;
}
return new EnumSetDeserializer(this, deser, _nullProvider, _unwrapSingle);
}
protected EnumSetDeserializer withResolved(ValueDeserializer<?> deser,
NullValueProvider nuller, Boolean unwrapSingle)
{
if ((Objects.equals(_unwrapSingle, unwrapSingle))
&& (_enumDeserializer == deser)
&& (_nullProvider == deser)) {
return this;
}
return new EnumSetDeserializer(this, deser, nuller, unwrapSingle);
}
/*
/**********************************************************************
/* Basic metadata
/**********************************************************************
*/
/**
* Because of costs associated with constructing Enum resolvers,
* let's cache instances by default.
*/
@Override
public boolean isCachable() {
// One caveat: content deserializer should prevent caching
return _enumType.getValueHandler() == null;
}
@Override // since 2.12
public LogicalType logicalType() {
return LogicalType.Collection;
}
@Override
public Boolean supportsUpdate(DeserializationConfig config) {
return Boolean.TRUE;
}
@Override
public Object getEmptyValue(DeserializationContext ctxt) {
return constructSet();
}
@Override
public AccessPattern getEmptyAccessPattern() {
return AccessPattern.DYNAMIC;
}
/*
/**********************************************************************
/* Contextualization
/**********************************************************************
*/
@Override
public ValueDeserializer<?> createContextual(DeserializationContext ctxt,
BeanProperty property)
{
// 07-May-2020, tatu: Is the argument `EnumSet.class` correct here?
// In a way seems like it should rather refer to value class... ?
// (as it's individual value of element type, not Container)...
final Boolean unwrapSingle = findFormatFeature(ctxt, property, EnumSet.class,
JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
ValueDeserializer<?> deser = _enumDeserializer;
if (deser == null) {
deser = ctxt.findContextualValueDeserializer(_enumType, property);
} else { // if directly assigned, probably not yet contextual, so:
deser = ctxt.handleSecondaryContextualization(deser, property, _enumType);
}
return withResolved(deser,
findContentNullProvider(ctxt, property, deser), unwrapSingle);
}
/*
/**********************************************************************
/* ValueDeserializer API
/**********************************************************************
*/
@Override
public EnumSet<?> deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException
{
EnumSet result = constructSet();
// Ok: must point to START_ARRAY (or equivalent)
if (!p.isExpectedStartArrayToken()) {
return handleNonArray(p, ctxt, result);
}
return _deserialize(p, ctxt, result);
}
@Override
public EnumSet<?> deserialize(JsonParser p, DeserializationContext ctxt,
EnumSet<?> result) throws JacksonException
{
// Ok: must point to START_ARRAY (or equivalent)
if (!p.isExpectedStartArrayToken()) {
return handleNonArray(p, ctxt, result);
}
return _deserialize(p, ctxt, result);
}
@SuppressWarnings("unchecked")
protected final EnumSet<?> _deserialize(JsonParser p, DeserializationContext ctxt,
EnumSet result) throws JacksonException
{
try {
JsonToken t;
while ((t = p.nextToken()) != JsonToken.END_ARRAY) {
// What to do with nulls? Fail or ignore? Fail, for now (note: would fail if we
// passed it to EnumDeserializer, too, but in general nulls should never be passed
// to non-container deserializers)
Enum<?> value;
if ((t == JsonToken.VALUE_NULL)
// [databind#5203]: Custom deserializer may return null for non-null token
|| (value = _enumDeserializer.deserialize(p, ctxt)) == null) {
value = (Enum<?>) _nullProvider.getNullValue(ctxt);
if (value == null) {
if (_skipNullValues) {
continue;
}
// EnumSet does not accept nulls, so we need to report an error
// 05-Nov-2025, tatu: In case of no explicit Nulls handling; with 2.x,
// let's just skip for backward compatibility; with 3.x, FAIL
// !!! TODO: retain PropertyName info
throw InvalidNullException.from(ctxt, null, _enumType);
}
}
result.add(value);
}
} catch (Exception e) {
throw DatabindException.wrapWithPath(ctxt, e,
new JacksonException.Reference(result, result.size()));
}
return result;
}
@Override
public Object deserializeWithType(JsonParser p, DeserializationContext ctxt,
TypeDeserializer typeDeserializer)
throws JacksonException
{
return typeDeserializer.deserializeTypedFromArray(p, ctxt);
}
@SuppressWarnings("unchecked")
private EnumSet constructSet()
{
return EnumSet.noneOf((Class<Enum>) _enumType.getRawClass());
}
@SuppressWarnings("unchecked")
protected EnumSet<?> handleNonArray(JsonParser p, DeserializationContext ctxt,
EnumSet result)
throws JacksonException
{
boolean canWrap = (_unwrapSingle == Boolean.TRUE) ||
((_unwrapSingle == null) &&
ctxt.isEnabled(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY));
if (!canWrap) {
return (EnumSet<?>) ctxt.handleUnexpectedToken(getValueType(ctxt), p);
}
// First: since `null`s not allowed, slightly simpler...
if (p.hasToken(JsonToken.VALUE_NULL)) {
return (EnumSet<?>) ctxt.handleUnexpectedToken(getValueType(ctxt), p);
}
try {
Enum<?> value = _enumDeserializer.deserialize(p, ctxt);
if (value != null) {
result.add(value);
}
} catch (Exception e) {
throw DatabindException.wrapWithPath(ctxt, e,
new JacksonException.Reference(result, result.size()));
}
return result;
}
}