| 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")); |
| } |
| } |