Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@JsonAnySetter on field annotation for Map #1249

Merged
merged 1 commit into from
Jun 2, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be good place to verify that we got a Map valued property; type should then be MapType as well (and guarantee there is content type to use).

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 @@ -138,11 +142,29 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx
return _valueDeserializer.deserialize(p, ctxt);
}

@SuppressWarnings("unchecked")
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);

// get the field value..
Object val = field.getValue(instance);

if (val != null) {
// 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)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. So actually type is verified here. So ignore my other comments; although checks should not be done at other points if and when it is verified here already,.

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,21 @@ 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);
assertNull(result.other);
}

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