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