package com.fasterxml.jackson.databind.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 com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
/**
* Test cases to verify that it is possible to define deserializers
* that can use contextual information (like field/method
* annotations) for configuration.
*/
@SuppressWarnings("serial")
public class TestContextualDeserialization extends BaseMapTest
{
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotation
public @interface Name {
public String value();
}
static class StringValue {
protected String value;
public StringValue(String v) { value = v; }
}
static class ContextualBean
{
@Name("NameA")
public StringValue a;
@Name("NameB")
public StringValue b;
}
static class ContextualCtorBean
{
protected String a, b;
@JsonCreator
public ContextualCtorBean(
@Name("CtorA") @JsonProperty("a") StringValue a,
@Name("CtorB") @JsonProperty("b") StringValue b)
{
this.a = a.value;
this.b = b.value;
}
}
@Name("Class")
static class ContextualClassBean
{
public StringValue a;
@Name("NameB")
public StringValue b;
}
static class ContextualArrayBean
{
@Name("array")
public StringValue[] beans;
}
static class ContextualListBean
{
@Name("list")
public List<StringValue> beans;
}
static class ContextualMapBean
{
@Name("map")
public Map<String, StringValue> beans;
}
static class MyContextualDeserializer
extends JsonDeserializer<StringValue>
implements ContextualDeserializer
{
protected final String _fieldName;
public MyContextualDeserializer() { this(""); }
public MyContextualDeserializer(String fieldName) {
_fieldName = fieldName;
}
@Override
public StringValue deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException
{
return new StringValue(""+_fieldName+"="+jp.getText());
}
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
BeanProperty property)
throws JsonMappingException
{
String name = (property == null) ? "NULL" : property.getName();
return new MyContextualDeserializer(name);
}
}
/**
* Alternative that uses annotation for choosing name to use
*/
static class AnnotatedContextualDeserializer
extends JsonDeserializer<StringValue>
implements ContextualDeserializer
{
protected final String _fieldName;
public AnnotatedContextualDeserializer() { this(""); }
public AnnotatedContextualDeserializer(String fieldName) {
_fieldName = fieldName;
}
@Override
public StringValue deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException
{
return new StringValue(""+_fieldName+"="+jp.getText());
}
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
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);
}
}
static class GenericStringDeserializer
extends StdScalarDeserializer<Object>
implements ContextualDeserializer
{
final String _value;
public GenericStringDeserializer() { this("N/A"); }
protected GenericStringDeserializer(String value) {
super(String.class);
_value = value;
}
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
return new GenericStringDeserializer(String.valueOf(ctxt.getContextualType().getRawClass().getSimpleName()));
}
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) {
return _value;
}
}
static class GenericBean {
@JsonDeserialize(contentUsing=GenericStringDeserializer.class)
public Map<Integer, String> stuff;
}
/*
/**********************************************************
/* Unit tests
/**********************************************************
*/
public void testSimple() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("test", Version.unknownVersion());
module.addDeserializer(StringValue.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,StringValue> 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,StringValue>> 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);
}
// for [databind#165]
public void testContextualType() throws Exception {
GenericBean bean = new ObjectMapper().readValue(aposToQuotes("{'stuff':{'1':'b'}}"),
GenericBean.class);
assertNotNull(bean.stuff);
assertEquals(1, bean.stuff.size());
assertEquals("String", bean.stuff.get(Integer.valueOf(1)));
}
/*
/**********************************************************
/* Helper methods
/**********************************************************
*/
private ObjectMapper _mapperWithAnnotatedContextual()
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("test", Version.unknownVersion());
module.addDeserializer(StringValue.class, new AnnotatedContextualDeserializer());
mapper.registerModule(module);
return mapper;
}
}