blob: 6b57205b50f1959e6512f89eeba88da3843b0243 [file] [log] [blame]
package com.fasterxml.jackson.databind.contextual;
import java.io.IOException;
import java.lang.annotation.*;
import java.util.*;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.fasterxml.jackson.databind.ser.ResolvableSerializer;
/**
* Test cases to verify that it is possible to define serializers
* that can use contextual information (like field/method
* annotations) for configuration.
*/
public class TestContextualSerialization extends BaseMapTest
{
// NOTE: important; MUST be considered a 'Jackson' annotation to be seen
// (or recognized otherwise via AnnotationIntrospect.isHandled())
@Target({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotation
public @interface Prefix {
public String value();
}
static class ContextualBean
{
protected final String _value;
public ContextualBean(String s) { _value = s; }
@Prefix("see:")
public String getValue() { return _value; }
}
// For [JACKSON-569]
static class AnnotatedContextualBean
{
@Prefix("prefix->")
@JsonSerialize(using=AnnotatedContextualSerializer.class)
protected final String value;
public AnnotatedContextualBean(String s) { value = s; }
}
@Prefix("wrappedBean:")
static class ContextualBeanWrapper
{
@Prefix("wrapped:")
public ContextualBean wrapped;
public ContextualBeanWrapper(String s) {
wrapped = new ContextualBean(s);
}
}
static class ContextualArrayBean
{
@Prefix("array->")
public final String[] beans;
public ContextualArrayBean(String... strings) {
beans = strings;
}
}
static class ContextualArrayElementBean
{
@Prefix("elem->")
@JsonSerialize(contentUsing=AnnotatedContextualSerializer.class)
public final String[] beans;
public ContextualArrayElementBean(String... strings) {
beans = strings;
}
}
static class ContextualListBean
{
@Prefix("list->")
public final List<String> beans = new ArrayList<String>();
public ContextualListBean(String... strings) {
for (String string : strings) {
beans.add(string);
}
}
}
static class ContextualMapBean
{
@Prefix("map->")
public final Map<String, String> beans = new HashMap<String, String>();
}
/**
* Another bean that has class annotations that should be visible for
* contextualizer, too
*/
@Prefix("Voila->")
static class BeanWithClassConfig
{
public String value;
public BeanWithClassConfig(String v) { value = v; }
}
/**
* Annotation-based contextual serializer that simply prepends piece of text.
*/
static class AnnotatedContextualSerializer
extends JsonSerializer<String>
implements ContextualSerializer
{
protected final String _prefix;
public AnnotatedContextualSerializer() { this(""); }
public AnnotatedContextualSerializer(String p) {
_prefix = p;
}
@Override
public void serialize(String value, JsonGenerator jgen, SerializerProvider provider) throws IOException
{
jgen.writeString(_prefix + value);
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
throws JsonMappingException
{
String prefix = "UNKNOWN";
Prefix ann = null;
if (property != null) {
ann = property.getAnnotation(Prefix.class);
if (ann == null) {
ann = property.getContextAnnotation(Prefix.class);
}
}
if (ann != null) {
prefix = ann.value();
}
return new AnnotatedContextualSerializer(prefix);
}
}
static class ContextualAndResolvable
extends JsonSerializer<String>
implements ContextualSerializer, ResolvableSerializer
{
protected int isContextual;
protected int isResolved;
public ContextualAndResolvable() { this(0, 0); }
public ContextualAndResolvable(int resolved, int contextual)
{
isContextual = contextual;
isResolved = resolved;
}
@Override
public void serialize(String value, JsonGenerator jgen, SerializerProvider provider) throws IOException
{
jgen.writeString("contextual="+isContextual+",resolved="+isResolved);
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
throws JsonMappingException
{
return new ContextualAndResolvable(isResolved, isContextual+1);
}
@Override
public void resolve(SerializerProvider provider) {
++isResolved;
}
}
static class AccumulatingContextual
extends JsonSerializer<String>
implements ContextualSerializer
{
protected String desc;
public AccumulatingContextual() { this(""); }
public AccumulatingContextual(String newDesc) {
desc = newDesc;
}
@Override
public void serialize(String value, JsonGenerator g, SerializerProvider provider) throws IOException
{
g.writeString(desc+"/"+value);
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
throws JsonMappingException
{
if (property == null) {
return new AccumulatingContextual(desc+"/ROOT");
}
return new AccumulatingContextual(desc+"/"+property.getName());
}
}
/*
/**********************************************************
/* Unit tests
/**********************************************************
*/
// Test to verify that contextual serializer can make use of property
// (method, field) annotations.
public void testMethodAnnotations() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("test", Version.unknownVersion());
module.addSerializer(String.class, new AnnotatedContextualSerializer());
mapper.registerModule(module);
assertEquals("{\"value\":\"see:foobar\"}", mapper.writeValueAsString(new ContextualBean("foobar")));
}
// Test to verify that contextual serializer can also use annotations
// for enclosing class.
public void testClassAnnotations() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("test", Version.unknownVersion());
module.addSerializer(String.class, new AnnotatedContextualSerializer());
mapper.registerModule(module);
assertEquals("{\"value\":\"Voila->xyz\"}", mapper.writeValueAsString(new BeanWithClassConfig("xyz")));
}
public void testWrappedBean() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("test", Version.unknownVersion());
module.addSerializer(String.class, new AnnotatedContextualSerializer());
mapper.registerModule(module);
assertEquals("{\"wrapped\":{\"value\":\"see:xyz\"}}", mapper.writeValueAsString(new ContextualBeanWrapper("xyz")));
}
// Serializer should get passed property context even if contained in an array.
public void testMethodAnnotationInArray() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("test", Version.unknownVersion());
module.addSerializer(String.class, new AnnotatedContextualSerializer());
mapper.registerModule(module);
ContextualArrayBean beans = new ContextualArrayBean("123");
assertEquals("{\"beans\":[\"array->123\"]}", mapper.writeValueAsString(beans));
}
// Serializer should get passed property context even if contained in a Collection.
public void testMethodAnnotationInList() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("test", Version.unknownVersion());
module.addSerializer(String.class, new AnnotatedContextualSerializer());
mapper.registerModule(module);
ContextualListBean beans = new ContextualListBean("abc");
assertEquals("{\"beans\":[\"list->abc\"]}", mapper.writeValueAsString(beans));
}
// Serializer should get passed property context even if contained in a Collection.
public void testMethodAnnotationInMap() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("test", Version.unknownVersion());
module.addSerializer(String.class, new AnnotatedContextualSerializer());
mapper.registerModule(module);
ContextualMapBean map = new ContextualMapBean();
map.beans.put("first", "In Map");
assertEquals("{\"beans\":{\"first\":\"map->In Map\"}}", mapper.writeValueAsString(map));
}
public void testContextualViaAnnotation() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
AnnotatedContextualBean bean = new AnnotatedContextualBean("abc");
assertEquals("{\"value\":\"prefix->abc\"}", mapper.writeValueAsString(bean));
}
public void testResolveOnContextual() throws Exception
{
SimpleModule module = new SimpleModule("test", Version.unknownVersion());
module.addSerializer(String.class, new ContextualAndResolvable());
ObjectMapper mapper = jsonMapperBuilder()
.addModule(module)
.build();
assertEquals(quote("contextual=1,resolved=1"), mapper.writeValueAsString("abc"));
// also: should NOT be called again
assertEquals(quote("contextual=1,resolved=1"), mapper.writeValueAsString("foo"));
}
public void testContextualArrayElement() throws Exception
{
ObjectMapper mapper = newJsonMapper();
ContextualArrayElementBean beans = new ContextualArrayElementBean("456");
assertEquals("{\"beans\":[\"elem->456\"]}", mapper.writeValueAsString(beans));
}
// Test to verify aspects of [databind#2429]
public void testRootContextualization2429() throws Exception
{
ObjectMapper mapper = jsonMapperBuilder()
.addModule(new SimpleModule("test", Version.unknownVersion())
.addSerializer(String.class, new AccumulatingContextual()))
.build();
assertEquals(quote("/ROOT/foo"), mapper.writeValueAsString("foo"));
assertEquals(quote("/ROOT/bar"), mapper.writeValueAsString("bar"));
assertEquals(quote("/ROOT/3"), mapper.writeValueAsString("3"));
}
}