1 package com.fasterxml.jackson.databind.ser; 2 3 import java.io.*; 4 import java.util.*; 5 6 import com.fasterxml.jackson.annotation.*; 7 8 import com.fasterxml.jackson.core.*; 9 import com.fasterxml.jackson.databind.*; 10 import com.fasterxml.jackson.databind.annotation.JsonSerialize; 11 import com.fasterxml.jackson.databind.jsontype.TypeSerializer; 12 import com.fasterxml.jackson.databind.module.SimpleModule; 13 import com.fasterxml.jackson.databind.ser.std.StdSerializer; 14 import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; 15 16 /** 17 * Unit tests for verifying serialization of simple basic non-structured 18 * types; primitives (and/or their wrappers), Strings. 19 */ 20 public class TestEnumSerialization 21 extends BaseMapTest 22 { 23 /** 24 * Test enumeration for verifying Enum serialization functionality. 25 */ 26 protected enum TestEnum { 27 A, B, C; TestEnum()28 private TestEnum() { } 29 toString()30 @Override public String toString() { return name().toLowerCase(); } 31 } 32 33 /** 34 * Alternative version that forces use of "toString-serializer". 35 */ 36 @JsonSerialize(using=ToStringSerializer.class) 37 protected enum AnnotatedTestEnum { 38 A2, B2, C2; AnnotatedTestEnum()39 private AnnotatedTestEnum() { } 40 toString()41 @Override public String toString() { return name().toLowerCase(); } 42 } 43 44 protected enum EnumWithJsonValue { 45 A("foo"), B("bar"); 46 private final String name; EnumWithJsonValue(String n)47 private EnumWithJsonValue(String n) { 48 name = n; 49 } 50 51 @Override toString()52 public String toString() { return name; } 53 54 @JsonValue external()55 public String external() { return "value:"+name; } 56 } 57 58 protected static interface ToStringMixin { 59 @Override toString()60 @JsonValue public String toString(); 61 } 62 63 protected static enum SerializableEnum implements JsonSerializable 64 { 65 A, B, C; 66 SerializableEnum()67 private SerializableEnum() { } 68 69 @Override serializeWithType(JsonGenerator jgen, SerializerProvider provider, TypeSerializer typeSer)70 public void serializeWithType(JsonGenerator jgen, SerializerProvider provider, TypeSerializer typeSer) 71 throws IOException, JsonProcessingException 72 { 73 serialize(jgen, provider); 74 } 75 76 @Override serialize(JsonGenerator jgen, SerializerProvider provider)77 public void serialize(JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException 78 { 79 jgen.writeString("foo"); 80 } 81 } 82 83 protected static enum LowerCaseEnum { 84 A, B, C; LowerCaseEnum()85 private LowerCaseEnum() { } 86 @Override toString()87 public String toString() { return name().toLowerCase(); } 88 } 89 90 static class MapBean { 91 public Map<TestEnum,Integer> map = new HashMap<TestEnum,Integer>(); 92 add(TestEnum key, int value)93 public void add(TestEnum key, int value) { 94 map.put(key, Integer.valueOf(value)); 95 } 96 } 97 98 static enum NOT_OK { 99 V1("v1"); 100 protected String key; 101 // any runtime-persistent annotation is fine NOT_OK(@sonProperty String key)102 NOT_OK(@JsonProperty String key) { this.key = key; } 103 } 104 105 static enum OK { 106 V1("v1"); 107 protected String key; OK(String key)108 OK(String key) { this.key = key; } 109 } 110 111 @SuppressWarnings({ "rawtypes", "serial" }) 112 static class LowerCasingEnumSerializer extends StdSerializer<Enum> 113 { LowerCasingEnumSerializer()114 public LowerCasingEnumSerializer() { super(Enum.class); } 115 @Override serialize(Enum value, JsonGenerator jgen, SerializerProvider provider)116 public void serialize(Enum value, JsonGenerator jgen, 117 SerializerProvider provider) throws IOException { 118 jgen.writeString(value.name().toLowerCase()); 119 } 120 } 121 122 protected static enum LC749Enum { 123 A, B, C; LC749Enum()124 private LC749Enum() { } 125 @Override toString()126 public String toString() { return name().toLowerCase(); } 127 } 128 129 // for [databind#1322] 130 protected enum EnumWithJsonProperty { 131 @JsonProperty("aleph") 132 A; 133 } 134 135 /* 136 /********************************************************************** 137 /* Test methods 138 /********************************************************************** 139 */ 140 141 private final ObjectMapper MAPPER = newJsonMapper(); 142 testSimple()143 public void testSimple() throws Exception 144 { 145 assertEquals("\"B\"", MAPPER.writeValueAsString(TestEnum.B)); 146 } 147 testEnumSet()148 public void testEnumSet() throws Exception 149 { 150 final EnumSet<TestEnum> value = EnumSet.of(TestEnum.B); 151 assertEquals("[\"B\"]", MAPPER.writeValueAsString(value)); 152 } 153 154 /** 155 * Whereas regular Enum serializer uses enum names, some users 156 * prefer calling toString() instead. So let's verify that 157 * this can be done using annotation for enum class. 158 */ testEnumUsingToString()159 public void testEnumUsingToString() throws Exception 160 { 161 assertEquals("\"c2\"", MAPPER.writeValueAsString(AnnotatedTestEnum.C2)); 162 } 163 testSubclassedEnums()164 public void testSubclassedEnums() throws Exception 165 { 166 assertEquals("\"B\"", MAPPER.writeValueAsString(EnumWithSubClass.B)); 167 } 168 testEnumsWithJsonValue()169 public void testEnumsWithJsonValue() throws Exception { 170 assertEquals("\"value:bar\"", MAPPER.writeValueAsString(EnumWithJsonValue.B)); 171 } 172 testEnumsWithJsonValueUsingMixin()173 public void testEnumsWithJsonValueUsingMixin() throws Exception 174 { 175 // can't share, as new mix-ins are added 176 ObjectMapper m = new ObjectMapper(); 177 m.addMixIn(TestEnum.class, ToStringMixin.class); 178 assertEquals("\"b\"", m.writeValueAsString(TestEnum.B)); 179 } 180 181 // [databind#601] testEnumsWithJsonValueInMap()182 public void testEnumsWithJsonValueInMap() throws Exception 183 { 184 EnumMap<EnumWithJsonValue,String> input = new EnumMap<EnumWithJsonValue,String>(EnumWithJsonValue.class); 185 input.put(EnumWithJsonValue.B, "x"); 186 // 24-Sep-2015, tatu: SHOULD actually use annotated method, as per: 187 assertEquals("{\"value:bar\":\"x\"}", MAPPER.writeValueAsString(input)); 188 } 189 190 /** 191 * Test for ensuring that @JsonSerializable is used with Enum types as well 192 * as with any other types. 193 */ testSerializableEnum()194 public void testSerializableEnum() throws Exception 195 { 196 assertEquals("\"foo\"", MAPPER.writeValueAsString(SerializableEnum.A)); 197 } 198 testToStringEnum()199 public void testToStringEnum() throws Exception 200 { 201 ObjectMapper m = new ObjectMapper(); 202 m.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, true); 203 assertEquals("\"b\"", m.writeValueAsString(LowerCaseEnum.B)); 204 205 // [databind#749] but should also be able to dynamically disable 206 assertEquals("\"B\"", 207 m.writer().without(SerializationFeature.WRITE_ENUMS_USING_TO_STRING) 208 .writeValueAsString(LowerCaseEnum.B)); 209 } 210 testToStringEnumWithEnumMap()211 public void testToStringEnumWithEnumMap() throws Exception 212 { 213 ObjectMapper m = new ObjectMapper(); 214 m.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); 215 EnumMap<LowerCaseEnum,String> enums = new EnumMap<LowerCaseEnum,String>(LowerCaseEnum.class); 216 enums.put(LowerCaseEnum.C, "value"); 217 assertEquals("{\"c\":\"value\"}", m.writeValueAsString(enums)); 218 } 219 testAsIndex()220 public void testAsIndex() throws Exception 221 { 222 // By default, serialize using name 223 ObjectMapper m = new ObjectMapper(); 224 assertFalse(m.isEnabled(SerializationFeature.WRITE_ENUMS_USING_INDEX)); 225 assertEquals(quote("B"), m.writeValueAsString(TestEnum.B)); 226 227 // but we can change (dynamically, too!) it to be number-based 228 m.enable(SerializationFeature.WRITE_ENUMS_USING_INDEX); 229 assertEquals("1", m.writeValueAsString(TestEnum.B)); 230 } 231 testAnnotationsOnEnumCtor()232 public void testAnnotationsOnEnumCtor() throws Exception 233 { 234 assertEquals(quote("V1"), MAPPER.writeValueAsString(OK.V1)); 235 assertEquals(quote("V1"), MAPPER.writeValueAsString(NOT_OK.V1)); 236 assertEquals(quote("V2"), MAPPER.writeValueAsString(NOT_OK2.V2)); 237 } 238 239 // [databind#227] testGenericEnumSerializer()240 public void testGenericEnumSerializer() throws Exception 241 { 242 // By default, serialize using name 243 ObjectMapper m = new ObjectMapper(); 244 SimpleModule module = new SimpleModule("foobar"); 245 module.addSerializer(Enum.class, new LowerCasingEnumSerializer()); 246 m.registerModule(module); 247 assertEquals(quote("b"), m.writeValueAsString(TestEnum.B)); 248 } 249 250 // [databind#749] 251 testEnumMapSerDefault()252 public void testEnumMapSerDefault() throws Exception { 253 final ObjectMapper mapper = newJsonMapper(); 254 EnumMap<LC749Enum, String> m = new EnumMap<LC749Enum, String>(LC749Enum.class); 255 m.put(LC749Enum.A, "value"); 256 assertEquals("{\"A\":\"value\"}", mapper.writeValueAsString(m)); 257 } 258 testEnumMapSerDisableToString()259 public void testEnumMapSerDisableToString() throws Exception { 260 final ObjectMapper mapper = new ObjectMapper(); 261 ObjectWriter w = mapper.writer().without(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); 262 EnumMap<LC749Enum, String> m = new EnumMap<LC749Enum, String>(LC749Enum.class); 263 m.put(LC749Enum.A, "value"); 264 assertEquals("{\"A\":\"value\"}", w.writeValueAsString(m)); 265 } 266 testEnumMapSerEnableToString()267 public void testEnumMapSerEnableToString() throws Exception { 268 final ObjectMapper mapper = new ObjectMapper(); 269 ObjectWriter w = mapper.writer().with(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); 270 EnumMap<LC749Enum, String> m = new EnumMap<LC749Enum, String>(LC749Enum.class); 271 m.put(LC749Enum.A, "value"); 272 assertEquals("{\"a\":\"value\"}", w.writeValueAsString(m)); 273 } 274 275 // [databind#1322] testEnumsWithJsonProperty()276 public void testEnumsWithJsonProperty() throws Exception { 277 assertEquals(quote("aleph"), MAPPER.writeValueAsString(EnumWithJsonProperty.A)); 278 } 279 280 // [databind#1535] testEnumKeysWithJsonProperty()281 public void testEnumKeysWithJsonProperty() throws Exception { 282 Map<EnumWithJsonProperty,Integer> input = new HashMap<EnumWithJsonProperty,Integer>(); 283 input.put(EnumWithJsonProperty.A, 13); 284 assertEquals(aposToQuotes("{'aleph':13}"), MAPPER.writeValueAsString(input)); 285 } 286 287 // [databind#1322] testEnumsWithJsonPropertyInSet()288 public void testEnumsWithJsonPropertyInSet() throws Exception 289 { 290 assertEquals("[\"aleph\"]", 291 MAPPER.writeValueAsString(EnumSet.of(EnumWithJsonProperty.A))); 292 } 293 294 // [databind#1322] testEnumsWithJsonPropertyAsKey()295 public void testEnumsWithJsonPropertyAsKey() throws Exception 296 { 297 EnumMap<EnumWithJsonProperty,String> input = new EnumMap<EnumWithJsonProperty,String>(EnumWithJsonProperty.class); 298 input.put(EnumWithJsonProperty.A, "b"); 299 assertEquals("{\"aleph\":\"b\"}", MAPPER.writeValueAsString(input)); 300 } 301 } 302 303 // [JACKSON-757], non-inner enum 304 enum NOT_OK2 { 305 V2("v2"); 306 protected String key; 307 // any runtime-persistent annotation is fine NOT_OK2(@sonProperty String key)308 NOT_OK2(@JsonProperty String key) { this.key = key; } 309 } 310