1 package com.fasterxml.jackson.databind.ser; 2 3 import java.io.IOException; 4 import java.util.*; 5 6 import com.fasterxml.jackson.annotation.*; 7 import com.fasterxml.jackson.core.JsonGenerator; 8 import com.fasterxml.jackson.databind.*; 9 import com.fasterxml.jackson.databind.annotation.JsonSerialize; 10 import com.fasterxml.jackson.databind.module.SimpleModule; 11 import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer; 12 import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; 13 14 /** 15 * This unit test suite tests functioning of {@link JsonValue} 16 * annotation with bean serialization. 17 */ 18 @SuppressWarnings("serial") 19 public class JsonValueTest 20 extends BaseMapTest 21 { 22 static class ValueClass<T> 23 { 24 final T _value; 25 ValueClass(T v)26 public ValueClass(T v) { _value = v; } 27 value()28 @JsonValue T value() { return _value; } 29 } 30 31 static class FieldValueClass<T> 32 { 33 @JsonValue(true) 34 final T _value; 35 FieldValueClass(T v)36 public FieldValueClass(T v) { _value = v; } 37 } 38 39 /** 40 * Another test class to check that it is also possible to 41 * force specific serializer to use with @JsonValue annotated 42 * method. Difference is between Integer serialization, and 43 * conversion to a Json String. 44 */ 45 final static class ToStringValueClass<T> 46 extends ValueClass<T> 47 { ToStringValueClass(T value)48 public ToStringValueClass(T value) { super(value); } 49 50 // Also, need to use this annotation to help 51 @JsonSerialize(using=ToStringSerializer.class) 52 @Override value()53 @JsonValue T value() { return super.value(); } 54 } 55 56 final static class ToStringValueClass2 57 extends ValueClass<String> 58 { ToStringValueClass2(String value)59 public ToStringValueClass2(String value) { super(value); } 60 61 // Simple as well, but let's ensure that other getters won't matter... 62 getFoobar()63 @JsonProperty int getFoobar() { return 4; } 64 getSomethingElse()65 public String[] getSomethingElse() { return new String[] { "1", "a" }; } 66 } 67 68 static class ValueBase { 69 public String a = "a"; 70 } 71 72 static class ValueType extends ValueBase { 73 public String b = "b"; 74 } 75 76 // Finally, let's also test static vs dynamic type 77 static class ValueWrapper { 78 @JsonValue getX()79 public ValueBase getX() { return new ValueType(); } 80 } 81 82 static class MapBean 83 { 84 @JsonValue toMap()85 public Map<String,String> toMap() 86 { 87 HashMap<String,String> map = new HashMap<String,String>(); 88 map.put("a", "1"); 89 return map; 90 } 91 } 92 93 static class MapFieldBean 94 { 95 @JsonValue 96 Map<String,String> stuff = new HashMap<>(); 97 { 98 stuff.put("b", "2"); 99 } 100 } 101 102 static class MapAsNumber extends HashMap<String,String> 103 { 104 @JsonValue value()105 public int value() { return 42; } 106 } 107 108 static class ListAsNumber extends ArrayList<Integer> 109 { 110 @JsonValue value()111 public int value() { return 13; } 112 } 113 114 // Just to ensure it's possible to disable annotation (usually 115 // via mix-ins, but here directly) 116 @JsonPropertyOrder({ "x", "y" }) 117 static class DisabledJsonValue { 118 @JsonValue(false) 119 public int x = 1; 120 121 @JsonValue(false) getY()122 public int getY() { return 2; } 123 } 124 125 static class IntExtBean { 126 public List<Internal> values = new ArrayList<Internal>(); 127 add(int v)128 public void add(int v) { values.add(new Internal(v)); } 129 } 130 131 static class Internal { 132 public int value; 133 Internal(int v)134 public Internal(int v) { value = v; } 135 136 @JsonValue asExternal()137 public External asExternal() { return new External(this); } 138 } 139 140 static class External { 141 public int i; 142 External(Internal e)143 External(Internal e) { i = e.value; } 144 } 145 146 // [databind#167] 147 148 @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "boingo") 149 @JsonSubTypes(value = {@JsonSubTypes.Type(name = "boopsy", value = AdditionInterfaceImpl.class) 150 }) 151 static interface AdditionInterface 152 { add(int in)153 public int add(int in); 154 } 155 156 public static class AdditionInterfaceImpl implements AdditionInterface 157 { 158 private final int toAdd; 159 160 @JsonCreator AdditionInterfaceImpl(@sonProperty"toAdd") int toAdd)161 public AdditionInterfaceImpl(@JsonProperty("toAdd") int toAdd) { 162 this.toAdd = toAdd; 163 } 164 165 @JsonProperty getToAdd()166 public int getToAdd() { 167 return toAdd; 168 } 169 170 @Override add(int in)171 public int add(int in) { 172 return in + toAdd; 173 } 174 } 175 176 static class Bean838 { 177 @JsonValue value()178 public String value() { 179 return "value"; 180 } 181 } 182 183 static class Bean838Serializer extends StdScalarSerializer<Bean838> 184 { Bean838Serializer()185 public Bean838Serializer() { 186 super(Bean838.class); 187 } 188 189 @Override serialize(Bean838 value, JsonGenerator gen, SerializerProvider provider)190 public void serialize(Bean838 value, JsonGenerator gen, 191 SerializerProvider provider) throws IOException { 192 gen.writeNumber(42); 193 } 194 } 195 196 /* 197 /********************************************************* 198 /* Test cases 199 /********************************************************* 200 */ 201 202 private final ObjectMapper MAPPER = new ObjectMapper(); 203 testSimpleMethodJsonValue()204 public void testSimpleMethodJsonValue() throws Exception 205 { 206 assertEquals("\"abc\"", MAPPER.writeValueAsString(new ValueClass<String>("abc"))); 207 assertEquals("null", MAPPER.writeValueAsString(new ValueClass<String>(null))); 208 } 209 testSimpleFieldJsonValue()210 public void testSimpleFieldJsonValue() throws Exception 211 { 212 assertEquals("\"abc\"", MAPPER.writeValueAsString(new FieldValueClass<String>("abc"))); 213 assertEquals("null", MAPPER.writeValueAsString(new FieldValueClass<String>(null))); 214 } 215 testJsonValueWithUseSerializer()216 public void testJsonValueWithUseSerializer() throws Exception 217 { 218 String result = serializeAsString(MAPPER, new ToStringValueClass<Integer>(Integer.valueOf(123))); 219 assertEquals("\"123\"", result); 220 } 221 222 /** 223 * Test for verifying that additional getters won't confuse serializer. 224 */ testMixedJsonValue()225 public void testMixedJsonValue() throws Exception 226 { 227 String result = serializeAsString(MAPPER, new ToStringValueClass2("xyz")); 228 assertEquals("\"xyz\"", result); 229 } 230 testDisabling()231 public void testDisabling() throws Exception 232 { 233 assertEquals(aposToQuotes("{'x':1,'y':2}"), 234 MAPPER.writeValueAsString(new DisabledJsonValue())); 235 } 236 testValueWithStaticType()237 public void testValueWithStaticType() throws Exception 238 { 239 // Ok; first, with dynamic type: 240 assertEquals("{\"a\":\"a\",\"b\":\"b\"}", MAPPER.writeValueAsString(new ValueWrapper())); 241 242 // then static 243 ObjectMapper staticMapper = jsonMapperBuilder() 244 .configure(MapperFeature.USE_STATIC_TYPING, true) 245 .build(); 246 assertEquals("{\"a\":\"a\"}", staticMapper.writeValueAsString(new ValueWrapper())); 247 } 248 testMapWithJsonValue()249 public void testMapWithJsonValue() throws Exception { 250 // First via method 251 assertEquals("{\"a\":\"1\"}", MAPPER.writeValueAsString(new MapBean())); 252 253 // then field 254 assertEquals("{\"b\":\"2\"}", MAPPER.writeValueAsString(new MapFieldBean())); 255 } 256 testWithMap()257 public void testWithMap() throws Exception { 258 assertEquals("42", MAPPER.writeValueAsString(new MapAsNumber())); 259 } 260 testWithList()261 public void testWithList() throws Exception { 262 assertEquals("13", MAPPER.writeValueAsString(new ListAsNumber())); 263 } 264 testInList()265 public void testInList() throws Exception { 266 IntExtBean bean = new IntExtBean(); 267 bean.add(1); 268 bean.add(2); 269 String json = MAPPER.writeValueAsString(bean); 270 assertEquals(json, "{\"values\":[{\"i\":1},{\"i\":2}]}"); 271 } 272 273 // [databind#167] testPolymorphicSerdeWithDelegate()274 public void testPolymorphicSerdeWithDelegate() throws Exception 275 { 276 AdditionInterface adder = new AdditionInterfaceImpl(1); 277 278 assertEquals(2, adder.add(1)); 279 String json = MAPPER.writeValueAsString(adder); 280 assertEquals("{\"boingo\":\"boopsy\",\"toAdd\":1}", json); 281 assertEquals(2, MAPPER.readValue(json, AdditionInterface.class).add(1)); 282 } 283 testJsonValueWithCustomOverride()284 public void testJsonValueWithCustomOverride() throws Exception 285 { 286 final Bean838 INPUT = new Bean838(); 287 288 // by default, @JsonValue should be used 289 assertEquals(quote("value"), MAPPER.writeValueAsString(INPUT)); 290 291 // but custom serializer should override it 292 ObjectMapper mapper = new ObjectMapper(); 293 mapper.registerModule(new SimpleModule() 294 .addSerializer(Bean838.class, new Bean838Serializer()) 295 ); 296 assertEquals("42", mapper.writeValueAsString(INPUT)); 297 } 298 } 299