blob: 9cc2d0f6d9b0b5023526548d1f760236b5bf2ece [file] [log] [blame]
package org.codehaus.jackson.map.contextual;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.*;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.Version;
import org.codehaus.jackson.annotate.JacksonAnnotation;
import org.codehaus.jackson.annotate.JsonCreator;
import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.map.*;
import org.codehaus.jackson.map.annotate.JsonDeserialize;
import org.codehaus.jackson.map.module.SimpleModule;
/**
* Test cases to verify that it is possible to define deserializers
* that can use contextual information (like field/method
* annotations) for configuration.
*
* @since 1.7
*/
public class TestContextualDeserialization extends BaseMapTest
{
/*
/**********************************************************
/* Helper classes
/**********************************************************
*/
/* NOTE: important; MUST be considered a 'Jackson' annotation to be seen
* (or recognized otherwise via AnnotationIntrospect.isHandled())
*/
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotation
public @interface Name {
public String value();
}
static class ContextualType {
protected String value;
public ContextualType(String v) { value = v; }
}
static class ContextualBean
{
@Name("NameA")
public ContextualType a;
@Name("NameB")
public ContextualType b;
}
static class ContextualCtorBean
{
protected String a, b;
@JsonCreator
public ContextualCtorBean(
@Name("CtorA") @JsonProperty("a") ContextualType a,
@Name("CtorB") @JsonProperty("b") ContextualType b)
{
this.a = a.value;
this.b = b.value;
}
}
@Name("Class")
static class ContextualClassBean
{
public ContextualType a;
@Name("NameB")
public ContextualType b;
}
static class AnnotatedContextualClassBean
{
@Name("xyz")
@JsonDeserialize(using=AnnotatedContextualDeserializer.class)
public ContextualType value;
}
static class ContextualArrayBean
{
@Name("array")
public ContextualType[] beans;
}
static class ContextualListBean
{
@Name("list")
public List<ContextualType> beans;
}
static class ContextualMapBean
{
@Name("map")
public Map<String, ContextualType> beans;
}
static class MyContextualDeserializer
extends JsonDeserializer<ContextualType>
implements ContextualDeserializer<ContextualType>
{
protected final String _fieldName;
public MyContextualDeserializer() { this(""); }
public MyContextualDeserializer(String fieldName) {
_fieldName = fieldName;
}
@Override
public ContextualType deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException
{
return new ContextualType(""+_fieldName+"="+jp.getText());
}
@Override
public JsonDeserializer<ContextualType> createContextual(DeserializationConfig config,
BeanProperty property)
throws JsonMappingException
{
return new MyContextualDeserializer(property.getName());
}
}
/**
* Alternative that uses annotation for choosing name to use
*/
static class AnnotatedContextualDeserializer
extends JsonDeserializer<ContextualType>
implements ContextualDeserializer<ContextualType>
{
protected final String _fieldName;
public AnnotatedContextualDeserializer() { this(""); }
public AnnotatedContextualDeserializer(String fieldName) {
_fieldName = fieldName;
}
@Override
public ContextualType deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException
{
return new ContextualType(""+_fieldName+"="+jp.getText());
}
@Override
public JsonDeserializer<ContextualType> createContextual(DeserializationConfig config,
BeanProperty property)
throws JsonMappingException
{
Name ann = property.getAnnotation(Name.class);
if (ann == null) {
ann = property.getContextAnnotation(Name.class);
}
String propertyName = (ann == null) ? "UNKNOWN" : ann.value();
return new MyContextualDeserializer(propertyName);
}
}
/*
/**********************************************************
/* Unit tests
/**********************************************************
*/
public void testSimple() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("test", Version.unknownVersion());
module.addDeserializer(ContextualType.class, new MyContextualDeserializer());
mapper.registerModule(module);
ContextualBean bean = mapper.readValue("{\"a\":\"1\",\"b\":\"2\"}", ContextualBean.class);
assertEquals("a=1", bean.a.value);
assertEquals("b=2", bean.b.value);
// try again, to ensure caching etc works
bean = mapper.readValue("{\"a\":\"3\",\"b\":\"4\"}", ContextualBean.class);
assertEquals("a=3", bean.a.value);
assertEquals("b=4", bean.b.value);
}
public void testSimpleWithAnnotations() throws Exception
{
ObjectMapper mapper = _mapperWithAnnotatedContextual();
ContextualBean bean = mapper.readValue("{\"a\":\"1\",\"b\":\"2\"}", ContextualBean.class);
assertEquals("NameA=1", bean.a.value);
assertEquals("NameB=2", bean.b.value);
// try again, to ensure caching etc works
bean = mapper.readValue("{\"a\":\"x\",\"b\":\"y\"}", ContextualBean.class);
assertEquals("NameA=x", bean.a.value);
assertEquals("NameB=y", bean.b.value);
}
public void testSimpleWithClassAnnotations() throws Exception
{
ObjectMapper mapper = _mapperWithAnnotatedContextual();
ContextualClassBean bean = mapper.readValue("{\"a\":\"1\",\"b\":\"2\"}", ContextualClassBean.class);
assertEquals("Class=1", bean.a.value);
assertEquals("NameB=2", bean.b.value);
// and again
bean = mapper.readValue("{\"a\":\"123\",\"b\":\"345\"}", ContextualClassBean.class);
assertEquals("Class=123", bean.a.value);
assertEquals("NameB=345", bean.b.value);
}
public void testAnnotatedCtor() throws Exception
{
ObjectMapper mapper = _mapperWithAnnotatedContextual();
ContextualCtorBean bean = mapper.readValue("{\"a\":\"foo\",\"b\":\"bar\"}", ContextualCtorBean.class);
assertEquals("CtorA=foo", bean.a);
assertEquals("CtorB=bar", bean.b);
bean = mapper.readValue("{\"a\":\"1\",\"b\":\"0\"}", ContextualCtorBean.class);
assertEquals("CtorA=1", bean.a);
assertEquals("CtorB=0", bean.b);
}
public void testAnnotatedArray() throws Exception
{
ObjectMapper mapper = _mapperWithAnnotatedContextual();
ContextualArrayBean bean = mapper.readValue("{\"beans\":[\"x\"]}", ContextualArrayBean.class);
assertEquals(1, bean.beans.length);
assertEquals("array=x", bean.beans[0].value);
bean = mapper.readValue("{\"beans\":[\"a\",\"b\"]}", ContextualArrayBean.class);
assertEquals(2, bean.beans.length);
assertEquals("array=a", bean.beans[0].value);
assertEquals("array=b", bean.beans[1].value);
}
public void testAnnotatedList() throws Exception
{
ObjectMapper mapper = _mapperWithAnnotatedContextual();
ContextualListBean bean = mapper.readValue("{\"beans\":[\"x\"]}", ContextualListBean.class);
assertEquals(1, bean.beans.size());
assertEquals("list=x", bean.beans.get(0).value);
bean = mapper.readValue("{\"beans\":[\"x\",\"y\",\"z\"]}", ContextualListBean.class);
assertEquals(3, bean.beans.size());
assertEquals("list=x", bean.beans.get(0).value);
assertEquals("list=y", bean.beans.get(1).value);
assertEquals("list=z", bean.beans.get(2).value);
}
public void testAnnotatedMap() throws Exception
{
ObjectMapper mapper = _mapperWithAnnotatedContextual();
ContextualMapBean bean = mapper.readValue("{\"beans\":{\"a\":\"b\"}}", ContextualMapBean.class);
assertEquals(1, bean.beans.size());
Map.Entry<String,ContextualType> entry = bean.beans.entrySet().iterator().next();
assertEquals("a", entry.getKey());
assertEquals("map=b", entry.getValue().value);
bean = mapper.readValue("{\"beans\":{\"x\":\"y\",\"1\":\"2\"}}", ContextualMapBean.class);
assertEquals(2, bean.beans.size());
Iterator<Map.Entry<String,ContextualType>> it = bean.beans.entrySet().iterator();
entry = it.next();
assertEquals("x", entry.getKey());
assertEquals("map=y", entry.getValue().value);
entry = it.next();
assertEquals("1", entry.getKey());
assertEquals("map=2", entry.getValue().value);
}
// ensure that direct associations also work
public void testAnnotatedContextual() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
AnnotatedContextualClassBean bean = mapper.readValue(
"{\"value\":\"a\"}",
AnnotatedContextualClassBean.class);
assertNotNull(bean);
assertEquals("xyz=a", bean.value.value);
}
/*
/**********************************************************
/* Helper methods
/**********************************************************
*/
private ObjectMapper _mapperWithAnnotatedContextual()
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("test", Version.unknownVersion());
module.addDeserializer(ContextualType.class, new AnnotatedContextualDeserializer());
mapper.registerModule(module);
return mapper;
}
}