Skip to content

Commit

Permalink
Merge pull request #1209 from LokeshN/Issue1082-Enums
Browse files Browse the repository at this point in the history
Issue 1082 - Enums with JsonCreator.Mode.Properties
  • Loading branch information
cowtowncoder committed Apr 21, 2016
2 parents b29965b + 8bc934e commit 092c09b
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1233,7 +1233,14 @@ public JsonDeserializer<?> createEnumDeserializer(DeserializationContext ctxt,
final Class<?> enumClass = type.getRawClass();
// 23-Nov-2010, tatu: Custom deserializer?
JsonDeserializer<?> deser = _findCustomEnumDeserializer(enumClass, config, beanDesc);


if (deser == null) {
ValueInstantiator valueInstantiator = _constructDefaultValueInstantiator(ctxt, beanDesc);
SettableBeanProperty[] creatorProps = null;
if (valueInstantiator != null) {
creatorProps = valueInstantiator.getFromObjectArguments(ctxt.getConfig());
}
// May have @JsonCreator for static factory method:
for (AnnotatedMethod factory : beanDesc.getFactoryMethods()) {
if (ctxt.getAnnotationIntrospector().hasCreatorAnnotation(factory)) {
Expand All @@ -1242,14 +1249,15 @@ public JsonDeserializer<?> createEnumDeserializer(DeserializationContext ctxt,
Class<?> returnType = factory.getRawReturnType();
// usually should be class, but may be just plain Enum<?> (for Enum.valueOf()?)
if (returnType.isAssignableFrom(enumClass)) {
deser = EnumDeserializer.deserializerForCreator(config, enumClass, factory);
deser = EnumDeserializer.deserializerForCreator(config, enumClass, factory, valueInstantiator, creatorProps);
break;
}
}
throw new IllegalArgumentException("Unsuitable method ("+factory+") decorated with @JsonCreator (for Enum type "
+enumClass.getName()+")");
}
}

// Need to consider @JsonValue if one found
if (deser == null) {
deser = new EnumDeserializer(constructEnumResolver(enumClass,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
package com.fasterxml.jackson.databind.deser.std;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
import com.fasterxml.jackson.databind.deser.impl.PropertyBasedCreator;
import com.fasterxml.jackson.databind.deser.impl.PropertyValueBuffer;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.CompactStringObjectMap;
import com.fasterxml.jackson.databind.util.EnumResolver;
import com.fasterxml.jackson.databind.util.TokenBuffer;

/**
* Deserializer class that can deserialize instances of
Expand Down Expand Up @@ -59,16 +65,17 @@ public EnumDeserializer(EnumResolver byNameResolver)
* @return Deserializer based on given factory method, if type was suitable;
* null if type can not be used
*/
public static JsonDeserializer<?> deserializerForCreator(DeserializationConfig config,
Class<?> enumClass, AnnotatedMethod factory)
{
// note: caller has verified there's just one arg; but we must verify its type
public static JsonDeserializer<?> deserializerForCreator(DeserializationConfig config, Class<?> enumClass,
AnnotatedMethod factory, ValueInstantiator valueInstantiator, SettableBeanProperty[] creatorProps) {
// note: caller has verified there's just one arg; but we must verify
// its type
Class<?> paramClass = factory.getRawParameterType(0);
if (config.canOverrideAccessModifiers()) {
ClassUtil.checkAndFixAccess(factory.getMember(),
config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
ClassUtil.checkAndFixAccess(factory.getMember(),
config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
}
return new FactoryBasedDeserializer(enumClass, factory, paramClass);
return new FactoryBasedDeserializer(enumClass, factory, paramClass).withCreatorProps(creatorProps)
.withValueInstantiator(valueInstantiator);
}

/*
Expand Down Expand Up @@ -232,6 +239,8 @@ protected static class FactoryBasedDeserializer
protected final Class<?> _inputType;
protected final Method _factory;
protected final JsonDeserializer<?> _deser;
protected ValueInstantiator _valueInstantiator;
protected SettableBeanProperty[] _creatorProps;

public FactoryBasedDeserializer(Class<?> cls, AnnotatedMethod f,
Class<?> inputType)
Expand All @@ -250,11 +259,26 @@ protected FactoryBasedDeserializer(FactoryBasedDeserializer base,
_deser = deser;
}

//To set the ValueInstantiator to be used for deserializing JsonCreator.MODE.Properties
public FactoryBasedDeserializer withValueInstantiator(ValueInstantiator valueInstantiator) {
_valueInstantiator = valueInstantiator;
return this;
}

//To set the SettableBeanProperty array to be used for deserializing JsonCreator.MODE.Properties
public FactoryBasedDeserializer withCreatorProps(SettableBeanProperty[] creatorProps) {
_creatorProps = creatorProps;
return this;
}

@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
BeanProperty property)
throws JsonMappingException
{
final DeserializationConfig config = ctxt.getConfig();
BeanDescription beanDesc = config.introspect(ctxt.constructType(_inputType));

if ((_deser == null) && (_inputType != String.class)) {
return new FactoryBasedDeserializer(this,
ctxt.findContextualValueDeserializer(ctxt.constructType(_inputType), property));
Expand All @@ -265,12 +289,22 @@ public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
Object value;
Object value = null;
if (_deser != null) {
value = _deser.deserialize(p, ctxt);
} else {
JsonToken curr = p.getCurrentToken();
if (curr == JsonToken.VALUE_STRING || curr == JsonToken.FIELD_NAME) {
//There can be a JSON object passed for deserializing an Enum,
//the below case handles it.
if (p.isExpectedStartObjectToken()) {
p.nextToken();

if(_creatorProps != null){
PropertyBasedCreator propertyBasedCreator = PropertyBasedCreator.construct(ctxt, _valueInstantiator, _creatorProps);
return deserializeEnumUsingPropertyBased(p, ctxt, propertyBasedCreator);
}
}
else if (curr == JsonToken.VALUE_STRING || curr == JsonToken.FIELD_NAME) {
value = p.getText();
} else {
value = p.getValueAsString();
Expand All @@ -294,5 +328,81 @@ public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, Typ
}
return typeDeserializer.deserializeTypedFromAny(p, ctxt);
}

// Method to deserialize the Enum using property based methodology
protected Object deserializeEnumUsingPropertyBased(final JsonParser p, final DeserializationContext ctxt,
final PropertyBasedCreator creator) throws IOException {
PropertyValueBuffer buffer = creator.startBuilding(p, ctxt, null);

JsonToken t = p.getCurrentToken();
for (; t == JsonToken.FIELD_NAME; t = p.nextToken()) {
String propName = p.getCurrentName();
p.nextToken(); // to point to value

SettableBeanProperty creatorProp = creator.findCreatorProperty(propName);
if (creatorProp != null) {

if (buffer.assignParameter(creatorProp, _deserializeWithErrorWrapping(p, ctxt, creatorProp))) {
p.nextToken(); // to move to next field name
}
continue;
}

if (buffer.readIdProperty(propName)) {
continue;
}

}

return creator.build(ctxt, buffer);
}

// ************ Got the below methods from BeanDeserializer ********************//

protected final Object _deserializeWithErrorWrapping(JsonParser p, DeserializationContext ctxt,
SettableBeanProperty prop) throws IOException {
try {
return prop.deserialize(p, ctxt);
} catch (Exception e) {
wrapAndThrow(e, _valueClass.getClass(), prop.getName(), ctxt);
// never gets here, unless caller declines to throw an exception
return null;
}
}

public void wrapAndThrow(Throwable t, Object bean, String fieldName, DeserializationContext ctxt)
throws IOException {
// [JACKSON-55] Need to add reference information
throw JsonMappingException.wrapWithPath(throwOrReturnThrowable(t, ctxt), bean, fieldName);
}

private Throwable throwOrReturnThrowable(Throwable t, DeserializationContext ctxt) throws IOException {
/*
* 05-Mar-2009, tatu: But one nasty edge is when we get
* StackOverflow: usually due to infinite loop. But that often gets
* hidden within an InvocationTargetException...
*/
while (t instanceof InvocationTargetException && t.getCause() != null) {
t = t.getCause();
}
// Errors to be passed as is
if (t instanceof Error) {
throw (Error) t;
}
boolean wrap = (ctxt == null) || ctxt.isEnabled(DeserializationFeature.WRAP_EXCEPTIONS);
// Ditto for IOExceptions; except we may want to wrap JSON
// exceptions
if (t instanceof IOException) {
if (!wrap || !(t instanceof JsonProcessingException)) {
throw (IOException) t;
}
} else if (!wrap) { // [JACKSON-407] -- allow disabling wrapping for
// unchecked exceptions
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
}
}
return t;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ public JsonDeserializer<?> findEnumDeserializer(final Class<?> type, final Deser
for (AnnotatedMethod am : factoryMethods) {
final JsonCreator creator = am.getAnnotation(JsonCreator.class);
if (creator != null) {
return EnumDeserializer.deserializerForCreator(config, type, am);
return EnumDeserializer.deserializerForCreator(config, type, am, null, null);
}
}
}
Expand Down Expand Up @@ -194,6 +194,41 @@ static enum EnumWithDefaultAnnoAndConstructor {
return null;
}
}

static enum EnumWithPropertiesModeJsonCreator {
TEST1,
TEST2,
TEST3;

@JsonGetter("name")
public String getName() {
return name();
}

@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public static EnumWithPropertiesModeJsonCreator create(@JsonProperty("name") String name) {
return EnumWithPropertiesModeJsonCreator.valueOf(name);
}

}

static enum EnumWithDelegateModeJsonCreator {
TEST1,
TEST2,
TEST3;

@JsonGetter("name")
public String getName() {
return name();
}

@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static EnumWithDelegateModeJsonCreator create(JsonNode json) {
return EnumWithDelegateModeJsonCreator.valueOf(json.get("name").asText());
}

}


/*
/**********************************************************
Expand Down Expand Up @@ -552,4 +587,27 @@ public void testEnumWithDefaultAnnotationWithConstructor() throws Exception {
EnumWithDefaultAnnoAndConstructor myEnum = mapper.readValue("\"foo\"", EnumWithDefaultAnnoAndConstructor.class);
assertNull("When using a constructor, the default value annotation shouldn't be used.", myEnum);
}

public void testJsonCreatorPropertiesWithEnum() throws Exception {
final ObjectMapper mapper = new ObjectMapper();

EnumWithPropertiesModeJsonCreator type1 = mapper.readValue("{\"name\":\"TEST1\", \"description\":\"TEST\"}", EnumWithPropertiesModeJsonCreator.class);
assertSame(EnumWithPropertiesModeJsonCreator.TEST1, type1);

EnumWithPropertiesModeJsonCreator type2 = mapper.readValue("{\"name\":\"TEST3\", \"description\":\"TEST\"}", EnumWithPropertiesModeJsonCreator.class);
assertSame(EnumWithPropertiesModeJsonCreator.TEST3, type2);

}

public void testJsonCreatorDelagateWithEnum() throws Exception {
final ObjectMapper mapper = new ObjectMapper();

EnumWithDelegateModeJsonCreator type1 = mapper.readValue("{\"name\":\"TEST1\", \"description\":\"TEST\"}", EnumWithDelegateModeJsonCreator.class);
assertSame(EnumWithDelegateModeJsonCreator.TEST1, type1);

EnumWithDelegateModeJsonCreator type2 = mapper.readValue("{\"name\":\"TEST3\", \"description\":\"TEST\"}", EnumWithDelegateModeJsonCreator.class);
assertSame(EnumWithDelegateModeJsonCreator.TEST3, type2);

}

}

0 comments on commit 092c09b

Please sign in to comment.