• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.fasterxml.jackson.databind.ser;
2 
3 import java.io.IOException;
4 import java.io.StringWriter;
5 import java.util.*;
6 
7 import javax.xml.parsers.DocumentBuilderFactory;
8 
9 import org.w3c.dom.Element;
10 
11 import com.fasterxml.jackson.annotation.JsonFilter;
12 import com.fasterxml.jackson.annotation.JsonFormat;
13 import com.fasterxml.jackson.annotation.JsonPropertyOrder;
14 import com.fasterxml.jackson.core.*;
15 import com.fasterxml.jackson.core.io.CharacterEscapes;
16 import com.fasterxml.jackson.databind.*;
17 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
18 import com.fasterxml.jackson.databind.module.SimpleModule;
19 import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
20 import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
21 import com.fasterxml.jackson.databind.ser.std.CollectionSerializer;
22 import com.fasterxml.jackson.databind.ser.std.StdDelegatingSerializer;
23 import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer;
24 import com.fasterxml.jackson.databind.util.StdConverter;
25 
26 /**
27  * Tests for verifying various issues with custom serializers.
28  */
29 @SuppressWarnings("serial")
30 public class TestCustomSerializers extends BaseMapTest
31 {
32     static class ElementSerializer extends JsonSerializer<Element>
33     {
34         @Override
serialize(Element value, JsonGenerator gen, SerializerProvider provider)35         public void serialize(Element value, JsonGenerator gen, SerializerProvider provider) throws IOException, JsonProcessingException {
36             gen.writeString("element");
37         }
38     }
39 
40     @JsonSerialize(using = ElementSerializer.class)
41     public static class ElementMixin {}
42 
43     public static class Immutable {
x()44         protected int x() { return 3; }
y()45         protected int y() { return 7; }
46     }
47 
48     /**
49      * Trivial simple custom escape definition set.
50      */
51     static class CustomEscapes extends CharacterEscapes
52     {
53         private final int[] _asciiEscapes;
54 
CustomEscapes()55         public CustomEscapes() {
56             _asciiEscapes = standardAsciiEscapesForJSON();
57             _asciiEscapes['a'] = 'A'; // to basically give us "\A" instead of 'a'
58             _asciiEscapes['b'] = CharacterEscapes.ESCAPE_STANDARD; // too force "\u0062"
59         }
60 
61         @Override
getEscapeCodesForAscii()62         public int[] getEscapeCodesForAscii() {
63             return _asciiEscapes;
64         }
65 
66         @Override
getEscapeSequence(int ch)67         public SerializableString getEscapeSequence(int ch) {
68             return null;
69         }
70     }
71 
72     @JsonFormat(shape=JsonFormat.Shape.OBJECT)
73     static class LikeNumber extends Number {
74         private static final long serialVersionUID = 1L;
75 
76         public int x;
77 
LikeNumber(int value)78         public LikeNumber(int value) { x = value; }
79 
80         @Override
doubleValue()81         public double doubleValue() {
82             return x;
83         }
84 
85         @Override
floatValue()86         public float floatValue() {
87             return x;
88         }
89 
90         @Override
intValue()91         public int intValue() {
92             return x;
93         }
94 
95         @Override
longValue()96         public long longValue() {
97             return x;
98         }
99     }
100 
101     // for [databind#631]
102     static class Issue631Bean
103     {
104         @JsonSerialize(using=ParentClassSerializer.class)
105         public Object prop;
106 
Issue631Bean(Object o)107         public Issue631Bean(Object o) {
108             prop = o;
109         }
110     }
111 
112     static class ParentClassSerializer
113         extends StdScalarSerializer<Object>
114     {
ParentClassSerializer()115         protected ParentClassSerializer() {
116             super(Object.class);
117         }
118 
119         @Override
serialize(Object value, JsonGenerator gen, SerializerProvider provider)120         public void serialize(Object value, JsonGenerator gen,
121                 SerializerProvider provider) throws IOException {
122             Object parent = gen.getCurrentValue();
123             String desc = (parent == null) ? "NULL" : parent.getClass().getSimpleName();
124             gen.writeString(desc+"/"+value);
125         }
126     }
127 
128     static class UCStringSerializer extends StdScalarSerializer<String>
129     {
UCStringSerializer()130         public UCStringSerializer() { super(String.class); }
131 
132         @Override
serialize(String value, JsonGenerator gen, SerializerProvider provider)133         public void serialize(String value, JsonGenerator gen,
134                 SerializerProvider provider) throws IOException {
135             gen.writeString(value.toUpperCase());
136         }
137     }
138 
139     // IMPORTANT: must associate serializer via property annotations
140     protected static class StringListWrapper
141     {
142         @JsonSerialize(contentUsing=UCStringSerializer.class)
143         public List<String> list;
144 
StringListWrapper(String... values)145         public StringListWrapper(String... values) {
146             list = new ArrayList<>();
147             for (String value : values) {
148                 list.add(value);
149             }
150         }
151     }
152 
153     // [databind#2475]
154     static class MyFilter2475 extends SimpleBeanPropertyFilter {
155         @Override
serializeAsField(Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer)156         public void serializeAsField(Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer) throws Exception {
157             // Ensure that "current value" remains pojo
158             final JsonStreamContext ctx = jgen.getOutputContext();
159             final Object curr = ctx.getCurrentValue();
160 
161             if (!(curr instanceof Item2475)) {
162                 throw new Error("Field '"+writer.getName()+"', context not that of `Item2475` instance");
163             }
164             super.serializeAsField(pojo, jgen, provider, writer);
165         }
166     }
167 
168     @JsonFilter("myFilter")
169     @JsonPropertyOrder({ "id", "set" })
170     public static class Item2475 {
171         private Collection<String> set;
172         private String id;
173 
Item2475(Collection<String> set, String id)174         public Item2475(Collection<String> set, String id) {
175             this.set = set;
176             this.id = id;
177         }
178 
getSet()179         public Collection<String> getSet() {
180             return set;
181         }
182 
getId()183         public String getId() {
184             return id;
185         }
186     }
187 
188     /*
189     /**********************************************************
190     /* Unit tests
191     /**********************************************************
192      */
193 
194     private final ObjectMapper MAPPER = new ObjectMapper();
195 
testCustomization()196     public void testCustomization() throws Exception
197     {
198         ObjectMapper objectMapper = new ObjectMapper();
199         objectMapper.addMixIn(Element.class, ElementMixin.class);
200         Element element = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument().createElement("el");
201         StringWriter sw = new StringWriter();
202         objectMapper.writeValue(sw, element);
203         assertEquals(sw.toString(), "\"element\"");
204     }
205 
206     @SuppressWarnings({ "unchecked", "rawtypes" })
testCustomLists()207     public void testCustomLists() throws Exception
208     {
209         ObjectMapper mapper = new ObjectMapper();
210         SimpleModule module = new SimpleModule("test", Version.unknownVersion());
211         JsonSerializer<?> ser = new CollectionSerializer(null, false, null, null);
212         final JsonSerializer<Object> collectionSerializer = (JsonSerializer<Object>) ser;
213 
214         module.addSerializer(Collection.class, new JsonSerializer<Collection>() {
215             @Override
216             public void serialize(Collection value, JsonGenerator gen, SerializerProvider provider)
217                     throws IOException
218             {
219                 if (value.size() != 0) {
220                     collectionSerializer.serialize(value, gen, provider);
221                 } else {
222                     gen.writeNull();
223                 }
224             }
225         });
226         mapper.registerModule(module);
227         assertEquals("null", mapper.writeValueAsString(new ArrayList<Object>()));
228     }
229 
230     // [databind#87]: delegating serializer
testDelegating()231     public void testDelegating() throws Exception
232     {
233         ObjectMapper mapper = new ObjectMapper();
234         SimpleModule module = new SimpleModule("test", Version.unknownVersion());
235         module.addSerializer(new StdDelegatingSerializer(Immutable.class,
236                 new StdConverter<Immutable, Map<String,Integer>>() {
237                     @Override
238                     public Map<String, Integer> convert(Immutable value)
239                     {
240                         HashMap<String,Integer> map = new LinkedHashMap<String,Integer>();
241                         map.put("x", value.x());
242                         map.put("y", value.y());
243                         return map;
244                     }
245         }));
246         mapper.registerModule(module);
247         assertEquals("{\"x\":3,\"y\":7}", mapper.writeValueAsString(new Immutable()));
248     }
249 
250     // [databind#215]: Allow registering CharacterEscapes via ObjectWriter
testCustomEscapes()251     public void testCustomEscapes() throws Exception
252     {
253         assertEquals(quote("foo\\u0062\\Ar"),
254                 MAPPER.writer(new CustomEscapes()).writeValueAsString("foobar"));
255     }
256 
testNumberSubclass()257     public void testNumberSubclass() throws Exception
258     {
259         assertEquals(aposToQuotes("{'x':42}"),
260                 MAPPER.writeValueAsString(new LikeNumber(42)));
261     }
262 
testWithCurrentValue()263     public void testWithCurrentValue() throws Exception
264     {
265         assertEquals(aposToQuotes("{'prop':'Issue631Bean/42'}"),
266                 MAPPER.writeValueAsString(new Issue631Bean(42)));
267     }
268 
testWithCustomElements()269     public void testWithCustomElements() throws Exception
270     {
271         // First variant that uses per-property override
272         StringListWrapper wr = new StringListWrapper("a", null, "b");
273         assertEquals(aposToQuotes("{'list':['A',null,'B']}"),
274                 MAPPER.writeValueAsString(wr));
275 
276         // and then per-type registration
277 
278         SimpleModule module = new SimpleModule("test", Version.unknownVersion());
279         module.addSerializer(String.class, new UCStringSerializer());
280         ObjectMapper mapper = new ObjectMapper()
281                 .registerModule(module);
282 
283         assertEquals(quote("FOOBAR"), mapper.writeValueAsString("foobar"));
284         assertEquals(aposToQuotes("['FOO',null]"),
285                 mapper.writeValueAsString(new String[] { "foo", null }));
286 
287         List<String> list = Arrays.asList("foo", null);
288         assertEquals(aposToQuotes("['FOO',null]"), mapper.writeValueAsString(list));
289 
290         Set<String> set = new LinkedHashSet<String>(Arrays.asList("foo", null));
291         assertEquals(aposToQuotes("['FOO',null]"), mapper.writeValueAsString(set));
292     }
293 
294     // [databind#2475]
testIssue2475()295     public void testIssue2475() throws Exception {
296         SimpleFilterProvider provider = new SimpleFilterProvider().addFilter("myFilter", new MyFilter2475());
297         ObjectWriter writer = MAPPER.writer(provider);
298 
299         // contents don't really matter that much as verification within filter but... let's
300         // check anyway
301         assertEquals(aposToQuotes("{'id':'ID-1','set':[]}"),
302                 writer.writeValueAsString(new Item2475(new ArrayList<String>(), "ID-1")));
303 
304         assertEquals(aposToQuotes("{'id':'ID-2','set':[]}"),
305                 writer.writeValueAsString(new Item2475(new HashSet<String>(), "ID-2")));
306     }
307 
308 }
309