Skip to content

Commit

Permalink
Implement [JACKSON-749]: Make @JsonValue the canonical serialization …
Browse files Browse the repository at this point in the history
…of Enums, so that deserializer also uses it
  • Loading branch information
cowtowncoder committed Jan 12, 2012
1 parent 0aef72d commit 703bf4a
Show file tree
Hide file tree
Showing 13 changed files with 155 additions and 56 deletions.
21 changes: 21 additions & 0 deletions release-notes/VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Version: 2.0.0

Release date:
xx-Feb-2012

Description:
The son of Jackson! Behold...

Fixes:

Improvements

* [JACKSON-749]: Make @JsonValue work for Enum deserialization

New features:

------------------------------------------------------------------------
=== History: ===
------------------------------------------------------------------------

[entries for versions 1.x and earlier not retained; refer to earlier releases)
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.fasterxml.jackson.databind.deser;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
Expand All @@ -13,6 +14,7 @@
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
import com.fasterxml.jackson.databind.type.*;
import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.EnumResolver;

/**
Expand Down Expand Up @@ -94,8 +96,7 @@ public abstract class BasicDeserializerFactory
= PrimitiveArrayDeserializers.getAll();

/**
* To support external/optional deserializers, we'll use this helper class
* (as per [JACKSON-386])
* To support external/optional deserializers, we'll use a helper class
*/
protected OptionalHandlerFactory optionalHandlers = OptionalHandlerFactory.instance;

Expand Down Expand Up @@ -258,7 +259,8 @@ public JsonDeserializer<?> createCollectionDeserializer(DeserializationConfig co
if (contentDeser == null) { // not defined by annotation
// One special type: EnumSet:
if (EnumSet.class.isAssignableFrom(collectionClass)) {
return new EnumSetDeserializer(constructEnumResolver(contentType.getRawClass(), config));
return new EnumSetDeserializer(constructEnumResolver(contentType.getRawClass(), config,
_findJsonValueFor(config, contentType)));
}
// But otherwise we can just use a generic value deserializer:
// 'null' -> collections have no referring fields
Expand Down Expand Up @@ -383,7 +385,8 @@ public JsonDeserializer<?> createMapDeserializer(DeserializationConfig config, D
if (kt == null || !kt.isEnum()) {
throw new IllegalArgumentException("Can not construct EnumMap; generic (key) type not available");
}
return new EnumMapDeserializer(constructEnumResolver(kt, config), contentDeser);
return new EnumMapDeserializer(constructEnumResolver(kt, config, _findJsonValueFor(config, keyType)),
contentDeser);
}

// Otherwise, generic handler works ok.
Expand Down Expand Up @@ -460,10 +463,7 @@ public JsonDeserializer<?> createEnumDeserializer(DeserializationConfig config,
JavaType type, BeanProperty property)
throws JsonMappingException
{
/* 18-Feb-2009, tatu: Must first check if we have a class annotation
* that should override default deserializer
*/
BasicBeanDescription beanDesc = config.introspectForCreation(type);
BasicBeanDescription beanDesc = config.introspect(type);
JsonDeserializer<?> des = findDeserializerFromAnnotation(config, beanDesc.getClassInfo(), property);
if (des != null) {
return des;
Expand All @@ -490,7 +490,8 @@ public JsonDeserializer<?> createEnumDeserializer(DeserializationConfig config,
+enumClass.getName()+")");
}
}
return new EnumDeserializer(constructEnumResolver(enumClass, config));
// [JACKSON-749] Also, need to consider @JsonValue, if one found
return new EnumDeserializer(constructEnumResolver(enumClass, config, beanDesc.findJsonValueMethod()));
}

@Override
Expand All @@ -511,8 +512,6 @@ public JsonDeserializer<?> createTreeDeserializer(DeserializationConfig config,
/**
* Method called by {@link BeanDeserializerFactory} to see if there might be a standard
* deserializer registered for given type.
*
* @since 1.8
*/
@SuppressWarnings("unchecked")
protected JsonDeserializer<Object> findStdBeanDeserializer(DeserializationConfig config,
Expand Down Expand Up @@ -601,8 +600,6 @@ public TypeDeserializer findTypeDeserializer(DeserializationConfig config, JavaT
* deserializer type will be this type or its subtype)
*
* @return Type deserializer to use for given base type, if one is needed; null if not.
*
* @since 1.5
*/
public TypeDeserializer findPropertyTypeDeserializer(DeserializationConfig config, JavaType baseType,
AnnotatedMember annotated, BeanProperty property)
Expand All @@ -629,8 +626,6 @@ public TypeDeserializer findPropertyTypeDeserializer(DeserializationConfig confi
*
* @param containerType Type of property; must be a container type
* @param propertyEntity Field or method that contains container property
*
* @since 1.5
*/
public TypeDeserializer findPropertyContentTypeDeserializer(DeserializationConfig config, JavaType containerType,
AnnotatedMember propertyEntity, BeanProperty property)
Expand Down Expand Up @@ -846,12 +841,29 @@ protected JavaType resolveType(DeserializationConfig config,
return type;
}

protected EnumResolver<?> constructEnumResolver(Class<?> enumClass, DeserializationConfig config)
protected EnumResolver<?> constructEnumResolver(Class<?> enumClass, DeserializationConfig config,
AnnotatedMethod jsonValueMethod)
{
if (jsonValueMethod != null) {
Method accessor = jsonValueMethod.getAnnotated();
if (config.canOverrideAccessModifiers()) {
ClassUtil.checkAndFixAccess(accessor);
}
return EnumResolver.constructUnsafeUsingMethod(enumClass, accessor);
}
// [JACKSON-212]: may need to use Enum.toString()
if (config.isEnabled(DeserializationConfig.Feature.READ_ENUMS_USING_TO_STRING)) {
return EnumResolver.constructUnsafeUsingToString(enumClass);
}
return EnumResolver.constructUnsafe(enumClass, config.getAnnotationIntrospector());
}

protected AnnotatedMethod _findJsonValueFor(DeserializationConfig config, JavaType enumType)
{
if (enumType == null) {
return null;
}
BasicBeanDescription beanDesc = config.introspect(enumType);
return beanDesc.findJsonValueMethod();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ protected SettableBeanProperty _resolveInnerClassValuedProperty(DeserializationC
for (Constructor<?> ctor : valueClass.getConstructors()) {
Class<?>[] paramTypes = ctor.getParameterTypes();
if (paramTypes.length == 1 && paramTypes[0] == enclosing) {
if (config.isEnabled(DeserializationConfig.Feature.CAN_OVERRIDE_ACCESS_MODIFIERS)) {
if (config.canOverrideAccessModifiers()) {
ClassUtil.checkAndFixAccess(ctor);
}
return new SettableBeanProperty.InnerClassProperty(prop, ctor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -748,7 +748,7 @@ protected ValueInstantiator constructDefaultValueInstantiator(DeserializationCon
BasicBeanDescription beanDesc)
throws JsonMappingException
{
boolean fixAccess = config.isEnabled(DeserializationConfig.Feature.CAN_OVERRIDE_ACCESS_MODIFIERS);
boolean fixAccess = config.canOverrideAccessModifiers();
CreatorCollector creators = new CreatorCollector(beanDesc, fixAccess);
AnnotationIntrospector intr = config.getAnnotationIntrospector();

Expand Down Expand Up @@ -1188,7 +1188,7 @@ protected void addInjectables(DeserializationConfig config,
{
Map<Object, AnnotatedMember> raw = beanDesc.findInjectables();
if (raw != null) {
boolean fixAccess = config.isEnabled(DeserializationConfig.Feature.CAN_OVERRIDE_ACCESS_MODIFIERS);
boolean fixAccess = config.canOverrideAccessModifiers();
for (Map.Entry<Object, AnnotatedMember> entry : raw.entrySet()) {
AnnotatedMember m = entry.getValue();
if (fixAccess) {
Expand All @@ -1209,7 +1209,7 @@ protected SettableAnyProperty constructAnySetter(DeserializationConfig config,
BasicBeanDescription beanDesc, AnnotatedMethod setter)
throws JsonMappingException
{
if (config.isEnabled(DeserializationConfig.Feature.CAN_OVERRIDE_ACCESS_MODIFIERS)) {
if (config.canOverrideAccessModifiers()) {
setter.fixAccess(); // to ensure we can call it
}
// we know it's a 2-arg method, second arg is the value
Expand Down Expand Up @@ -1249,7 +1249,7 @@ protected SettableBeanProperty constructSettableProperty(DeserializationConfig c
throws JsonMappingException
{
// need to ensure method is callable (for non-public)
if (config.isEnabled(DeserializationConfig.Feature.CAN_OVERRIDE_ACCESS_MODIFIERS)) {
if (config.canOverrideAccessModifiers()) {
setter.fixAccess();
}

Expand Down Expand Up @@ -1286,7 +1286,7 @@ protected SettableBeanProperty constructSettableProperty(DeserializationConfig c
throws JsonMappingException
{
// need to ensure method is callable (for non-public)
if (config.isEnabled(DeserializationConfig.Feature.CAN_OVERRIDE_ACCESS_MODIFIERS)) {
if (config.canOverrideAccessModifiers()) {
field.fixAccess();
}
JavaType t0 = beanDesc.bindingsForBeanType().resolveType(field.getGenericType());
Expand Down Expand Up @@ -1327,7 +1327,7 @@ protected SettableBeanProperty constructSetterlessProperty(DeserializationConfig
throws JsonMappingException
{
// need to ensure it is callable now:
if (config.isEnabled(DeserializationConfig.Feature.CAN_OVERRIDE_ACCESS_MODIFIERS)) {
if (config.canOverrideAccessModifiers()) {
getter.fixAccess();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public static JsonDeserializer<?> deserializerForCreator(DeserializationConfig c
if (factory.getParameterType(0) != String.class) {
throw new IllegalArgumentException("Parameter #0 type for factory method ("+factory+") not suitable, must be java.lang.String");
}
if (config.isEnabled(DeserializationConfig.Feature.CAN_OVERRIDE_ACCESS_MODIFIERS)) {
if (config.canOverrideAccessModifiers()) {
ClassUtil.checkAndFixAccess(factory.getMember());
}
return new FactoryBasedDeserializer(enumClass, factory);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public static KeyDeserializer findStringBasedKeyDeserializer(DeserializationConf
// Ok, so: can we find T(String) constructor?
Constructor<?> ctor = beanDesc.findSingleArgConstructor(String.class);
if (ctor != null) {
if (config.isEnabled(DeserializationConfig.Feature.CAN_OVERRIDE_ACCESS_MODIFIERS)) {
if (config.canOverrideAccessModifiers()) {
ClassUtil.checkAndFixAccess(ctor);
}
return new StdKeyDeserializer.StringCtorKeyDeserializer(ctor);
Expand All @@ -89,7 +89,7 @@ public static KeyDeserializer findStringBasedKeyDeserializer(DeserializationConf
*/
Method m = beanDesc.findFactoryMethod(String.class);
if (m != null){
if (config.isEnabled(DeserializationConfig.Feature.CAN_OVERRIDE_ACCESS_MODIFIERS)) {
if (config.canOverrideAccessModifiers()) {
ClassUtil.checkAndFixAccess(m);
}
return new StdKeyDeserializer.StringFactoryKeyDeserializer(m);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ public static BasicBeanDescription forDeserialization(POJOPropertiesCollector co
desc._anySetterMethod = coll.getAnySetterMethod();
desc._ignoredPropertyNames = coll.getIgnoredPropertyNames();
desc._injectables = coll.getInjectables();
desc._jsonValueMethod = coll.getJsonValueMethod();
return desc;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
public abstract class BasicSerializerFactory
extends SerializerFactory
{

/*
/**********************************************************
/* Configuration, lookup tables/maps
Expand Down Expand Up @@ -256,7 +255,7 @@ public final JsonSerializer<?> findSerializerByPrimaryType(JavaType type, Serial
if (valueMethod != null) {
// [JACKSON-586]: need to ensure accessibility of method
Method m = valueMethod.getAnnotated();
if (config.isEnabled(SerializationConfig.Feature.CAN_OVERRIDE_ACCESS_MODIFIERS)) {
if (config.canOverrideAccessModifiers()) {
ClassUtil.checkAndFixAccess(m);
}
JsonSerializer<Object> ser = findSerializerFromAnnotation(config, valueMethod, property);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,12 @@ public static class ConfigImpl extends Config
/**
* List of providers for additional serializers, checked before considering default
* basic or bean serialializers.
*
* @since 1.7
*/
protected final Serializers[] _additionalSerializers;

/**
* @since 1.8
* List of providers for additional key serializers, checked before considering default
* key serialializers.
*/
protected final Serializers[] _additionalKeySerializers;

Expand Down Expand Up @@ -450,7 +449,7 @@ protected JsonSerializer<Object> constructBeanSerializer(SerializationConfig con

AnnotatedMethod anyGetter = beanDesc.findAnyGetter();
if (anyGetter != null) { // since 1.6
if (config.isEnabled(SerializationConfig.Feature.CAN_OVERRIDE_ACCESS_MODIFIERS)) {
if (config.canOverrideAccessModifiers()) {
anyGetter.fixAccess();
}
JavaType type = anyGetter.getType(beanDesc.bindingsForBeanType());
Expand Down Expand Up @@ -720,7 +719,7 @@ protected BeanPropertyWriter _constructWriter(SerializationConfig config, TypeBi
PropertyBuilder pb, boolean staticTyping, String name, AnnotatedMember accessor)
throws JsonMappingException
{
if (config.isEnabled(SerializationConfig.Feature.CAN_OVERRIDE_ACCESS_MODIFIERS)) {
if (config.canOverrideAccessModifiers()) {
accessor.fixAccess();
}
JavaType type = accessor.getType(typeContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ protected Object getDefaultBean()
/* If we can fix access rights, we should; otherwise non-public
* classes or default constructor will prevent instantiation
*/
_defaultBean = _beanDesc.instantiateBean(_config.isEnabled(SerializationConfig.Feature.CAN_OVERRIDE_ACCESS_MODIFIERS));
_defaultBean = _beanDesc.instantiateBean(_config.canOverrideAccessModifiers());
if (_defaultBean == null) {
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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.fasterxml.jackson.databind.AnnotationIntrospector;

import java.lang.reflect.Method;
import java.util.*;

/**
Expand Down Expand Up @@ -55,6 +56,26 @@ public static <ET extends Enum<ET>> EnumResolver<ET> constructUsingToString(Clas
}
return new EnumResolver<ET>(enumCls, enumValues, map);
}

public static <ET extends Enum<ET>> EnumResolver<ET> constructUsingMethod(Class<ET> enumCls,
Method accessor)
{
ET[] enumValues = enumCls.getEnumConstants();
HashMap<String, ET> map = new HashMap<String, ET>();
// from last to first, so that in case of duplicate values, first wins
for (int i = enumValues.length; --i >= 0; ) {
ET en = enumValues[i];
try {
Object o = accessor.invoke(en);
if (o != null) {
map.put(o.toString(), en);
}
} catch (Exception e) {
throw new IllegalArgumentException("Failed to access @JsonValue of Enum value "+en+": "+e.getMessage());
}
}
return new EnumResolver<ET>(enumCls, enumValues, map);
}

/**
* This method is needed because of the dynamic nature of constructing Enum
Expand All @@ -81,6 +102,18 @@ public static EnumResolver<?> constructUnsafeUsingToString(Class<?> rawEnumCls)
Class<Enum> enumCls = (Class<Enum>) rawEnumCls;
return constructUsingToString(enumCls);
}

/**
* Method used when actual String serialization is indicated using @JsonValue
* on a method.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public static EnumResolver<?> constructUnsafeUsingMethod(Class<?> rawEnumCls, Method accessor)
{
// wrong as ever but:
Class<Enum> enumCls = (Class<Enum>) rawEnumCls;
return constructUsingMethod(enumCls, accessor);
}

public T findEnum(String key)
{
Expand Down
Loading

0 comments on commit 703bf4a

Please sign in to comment.