Skip to content

Commit

Permalink
@JsonAnySetter on field annotation for Map
Browse files Browse the repository at this point in the history
Supporting @JsonAnySetter annotation on java.util.Map
  • Loading branch information
LokeshN committed May 25, 2016
1 parent 127e053 commit f377459
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 13 deletions.
10 changes: 10 additions & 0 deletions src/main/java/com/fasterxml/jackson/databind/BeanDescription.java
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,16 @@ protected BeanDescription(JavaType type) {
*/
public abstract AnnotatedMethod findAnySetter();

/**
* Method used to locate the field of the class that implements
* {@link com.fasterxml.jackson.annotation.JsonAnySetter} If no such method
* exists null is returned. If more than one are found, an exception is
* thrown.
*
* @since 2.8
*/
public abstract AnnotatedMember findAnySetterField();

/**
* Method for locating the getter method that is annotated with
* {@link com.fasterxml.jackson.annotation.JsonValue} annotation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -469,13 +469,20 @@ protected void addBeanProps(DeserializationContext ctxt,
}

// Also, do we have a fallback "any" setter?
AnnotatedMethod anySetter = beanDesc.findAnySetter();
if (anySetter != null) {
builder.setAnySetter(constructAnySetter(ctxt, beanDesc, anySetter));
AnnotatedMethod anySetterMethod = beanDesc.findAnySetter();
AnnotatedMember anySetterField = null;
if (anySetterMethod != null) {
builder.setAnySetter(constructAnySetter(ctxt, beanDesc, anySetterMethod));
}
else {
anySetterField = beanDesc.findAnySetterField();
if(anySetterField != null) {
builder.setAnySetter(constructAnySetter(ctxt, beanDesc, anySetterField));
}
}
// NOTE: we do NOT add @JsonIgnore'd properties into blocked ones if there's any-setter
// Implicit ones via @JsonIgnore and equivalent?
if (anySetter == null) {
if (anySetterMethod == null && anySetterField == null) {
Collection<String> ignored2 = beanDesc.getIgnoredPropertyNames();
if (ignored2 != null) {
for (String propName : ignored2) {
Expand Down Expand Up @@ -670,14 +677,23 @@ protected void addInjectables(DeserializationContext ctxt,
* has been designated as such setter.
*/
protected SettableAnyProperty constructAnySetter(DeserializationContext ctxt,
BeanDescription beanDesc, AnnotatedMethod setter)
BeanDescription beanDesc, AnnotatedMember setter)
throws JsonMappingException
{
if (ctxt.canOverrideAccessModifiers()) {
setter.fixAccess(ctxt.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS)); // to ensure we can call it
}
// we know it's a 2-arg method, second arg is the value
JavaType type = setter.getParameterType(1);

//find the java type based on the annotated setter method or setter field
JavaType type = null;
if (setter instanceof AnnotatedMethod) {
// we know it's a 2-arg method, second arg is the value
type = ((AnnotatedMethod) setter).getParameterType(1);
} else if (setter instanceof AnnotatedField) {
// get the type from the content type of the map object
type = ((AnnotatedField) setter).getType().getContentType();
}

BeanProperty.Std property = new BeanProperty.Std(PropertyName.construct(setter.getName()),
type, null, beanDesc.getClassAnnotations(), setter,
PropertyMetadata.STD_OPTIONAL);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package com.fasterxml.jackson.databind.deser;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.impl.ReadableObjectId.Referring;
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;

Expand All @@ -30,8 +34,8 @@ public class SettableAnyProperty
/**
* Annotated variant is needed for JDK serialization only
*/
final protected AnnotatedMethod _setter;

final protected AnnotatedMember _setter;
protected final JavaType _type;

protected JsonDeserializer<Object> _valueDeserializer;
Expand All @@ -44,7 +48,7 @@ public class SettableAnyProperty
/**********************************************************
*/

public SettableAnyProperty(BeanProperty property, AnnotatedMethod setter, JavaType type,
public SettableAnyProperty(BeanProperty property, AnnotatedMember setter, JavaType type,
JsonDeserializer<Object> valueDeser, TypeDeserializer typeDeser)
{
_property = property;
Expand All @@ -53,7 +57,7 @@ public SettableAnyProperty(BeanProperty property, AnnotatedMethod setter, JavaTy
_valueDeserializer = valueDeser;
_valueTypeDeserializer = typeDeser;
}

public SettableAnyProperty withValueDeserializer(JsonDeserializer<Object> deser) {
return new SettableAnyProperty(_property, _setter, _type,
deser, _valueTypeDeserializer);
Expand Down Expand Up @@ -141,8 +145,31 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx
public void set(Object instance, String propName, Object value) throws IOException
{
try {
// note: can not use 'setValue()' due to taking 2 args
_setter.getAnnotated().invoke(instance, propName, value);
// note: can not use 'setValue()' due to taking 2 args
if (_setter instanceof AnnotatedMethod) {
((AnnotatedMethod) _setter).getAnnotated().invoke(instance, propName, value);
}
// if annotation in the field (only map is supported now)
else if (_setter instanceof AnnotatedField) {
AnnotatedField field = ((AnnotatedField) _setter);
// check whether the annotated field is an instance of Map, if
// not throw Error
if (!Map.class.isAssignableFrom(field.getRawType())) {
throw new IllegalArgumentException(
"Expecting a java.util.Map type in JsonAnySetter field, but got " + field.getRawType());
}
// get the field value, if null instantiate it..
Object val = field.getValue(instance);
if (val == null) {
val = new HashMap();
}

// add the property key and value
((Map)val).put(propName, value);

// set the value back to the field
field.setValue(instance, val);
}
} catch (Exception e) {
_throwAsIOE(e, propName, value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,22 @@ public AnnotatedMember findAnyGetter() throws IllegalArgumentException
}
return anyGetter;
}

public AnnotatedMember findAnySetterField() throws IllegalArgumentException {
AnnotatedMember anySetter = (_propCollector == null) ? null : _propCollector.getAnySetterField();
if (anySetter != null) {
/*
* For now let's require a Map; in future can add support for other
* types like perhaps Iterable<Map.Entry>?
*/
Class<?> type = anySetter.getRawType();
if (!Map.class.isAssignableFrom(type)) {
throw new IllegalArgumentException("Invalid 'any-setter' annotation on field " + anySetter.getName()
+ "(): type is not instance of java.util.Map");
}
}
return anySetter;
}

@Override
public Map<String,AnnotatedMember> findBackReferenceProperties()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.lang.reflect.Modifier;
import java.util.*;

import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.cfg.HandlerInstantiator;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
Expand Down Expand Up @@ -83,6 +84,8 @@ public class POJOPropertiesCollector
protected LinkedList<AnnotatedMember> _anyGetters;

protected LinkedList<AnnotatedMethod> _anySetters;

protected LinkedList<AnnotatedMember> _anySetterField;

/**
* Method(s) marked with 'JsonValue' annotation
Expand Down Expand Up @@ -194,6 +197,21 @@ public AnnotatedMember getAnyGetter()
}
return null;
}

public AnnotatedMember getAnySetterField()
{
if (!_collected) {
collectAll();
}
if (_anySetterField != null) {
if (_anySetterField.size() > 1) {
reportProblem("Multiple 'any-Setters' defined ("+_anySetters.get(0)+" vs "
+_anySetterField.get(1)+")");
}
return _anySetterField.getFirst();
}
return null;
}

public AnnotatedMethod getAnySetterMethod()
{
Expand Down Expand Up @@ -401,6 +419,15 @@ protected void _addFields(Map<String, POJOPropertyBuilder> props)
if (pruneFinalFields && (pn == null) && !ignored && Modifier.isFinal(f.getModifiers())) {
continue;
}

//if field has annotation @JsonAnySetter
if(f.hasAnnotation(JsonAnySetter.class)) {
if (_anySetterField == null) {
_anySetterField = new LinkedList<AnnotatedMember>();
}
_anySetterField.add(f);
}

_property(props, implName).addField(f, pn, nameExplicit, visible, ignored);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,33 @@ public void prop(String name, Base value) {
}
}

static class JsonAnySetterOnMap {
public int id;

@JsonAnySetter
protected HashMap<String, String> other = new HashMap<String, String>();

@JsonAnyGetter
public Map<String, String> any() {
return other;
}

}

static class JsonAnySetterOnNullMap {
public int id;

@JsonAnySetter
protected HashMap<String, String> other;

@JsonAnyGetter
public Map<String, String> any() {
return other;
}

}


/*
/**********************************************************
/* Test methods
Expand Down Expand Up @@ -224,6 +251,22 @@ public void testPolymorphic() throws Exception
assertEquals("xyz", ((Impl) ob).value);
}

public void testJsonAnySetterOnMap() throws Exception {
JsonAnySetterOnMap result = MAPPER.readValue("{\"id\":2,\"name\":\"Joe\", \"city\":\"New Jersey\"}",
JsonAnySetterOnMap.class);
assertEquals(2, result.id);
assertEquals("Joe", result.other.get("name"));
assertEquals("New Jersey", result.other.get("city"));
}

public void testJsonAnySetterOnNullMap() throws Exception {
JsonAnySetterOnNullMap result = MAPPER.readValue("{\"id\":2,\"name\":\"Joe\", \"city\":\"New Jersey\"}",
JsonAnySetterOnNullMap.class);
assertEquals(2, result.id);
assertEquals("Joe", result.other.get("name"));
assertEquals("New Jersey", result.other.get("city"));
}

/*
/**********************************************************
/* Private helper methods
Expand Down

0 comments on commit f377459

Please sign in to comment.