1 package com.fasterxml.jackson.databind.ser.std; 2 3 import java.io.IOException; 4 import java.util.Calendar; 5 import java.util.Date; 6 7 import com.fasterxml.jackson.core.*; 8 import com.fasterxml.jackson.databind.*; 9 import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper; 10 import com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap; 11 import com.fasterxml.jackson.databind.util.ClassUtil; 12 import com.fasterxml.jackson.databind.util.EnumValues; 13 14 @SuppressWarnings("serial") 15 public abstract class StdKeySerializers 16 { 17 @SuppressWarnings("deprecation") 18 protected final static JsonSerializer<Object> DEFAULT_KEY_SERIALIZER = new StdKeySerializer(); 19 20 protected final static JsonSerializer<Object> DEFAULT_STRING_SERIALIZER = new StringKeySerializer(); 21 22 /** 23 * @param config Serialization configuration in use, may be needed in choosing 24 * serializer to use 25 * @param rawKeyType Type of key values to serialize 26 * @param useDefault If no match is found, should we return fallback deserializer 27 * (true), or null (false)? 28 */ getStdKeySerializer(SerializationConfig config, Class<?> rawKeyType, boolean useDefault)29 public static JsonSerializer<Object> getStdKeySerializer(SerializationConfig config, 30 Class<?> rawKeyType, boolean useDefault) 31 { 32 // 24-Sep-2015, tatu: Important -- should ONLY consider types for which `@JsonValue` 33 // cannot be used, since caller has not yet checked for that annotation 34 // This is why Enum types are not handled here quite yet 35 36 // [databind#943: Use a dynamic key serializer if we are not given actual 37 // type declaration 38 if ((rawKeyType == null) || (rawKeyType == Object.class)) { 39 return new Dynamic(); 40 } 41 if (rawKeyType == String.class) { 42 return DEFAULT_STRING_SERIALIZER; 43 } 44 if (rawKeyType.isPrimitive()) { 45 rawKeyType = ClassUtil.wrapperType(rawKeyType); 46 } 47 if (rawKeyType == Integer.class) { 48 return new Default(Default.TYPE_INTEGER, rawKeyType); 49 } 50 if (rawKeyType == Long.class) { 51 return new Default(Default.TYPE_LONG, rawKeyType); 52 } 53 if (rawKeyType.isPrimitive() || Number.class.isAssignableFrom(rawKeyType)) { 54 // 28-Jun-2016, tatu: Used to just return DEFAULT_KEY_SERIALIZER, but makes 55 // more sense to use simpler one directly 56 return new Default(Default.TYPE_TO_STRING, rawKeyType); 57 } 58 if (rawKeyType == Class.class) { 59 return new Default(Default.TYPE_CLASS, rawKeyType); 60 } 61 if (Date.class.isAssignableFrom(rawKeyType)) { 62 return new Default(Default.TYPE_DATE, rawKeyType); 63 } 64 if (Calendar.class.isAssignableFrom(rawKeyType)) { 65 return new Default(Default.TYPE_CALENDAR, rawKeyType); 66 } 67 // other JDK types we know convert properly with 'toString()'? 68 if (rawKeyType == java.util.UUID.class) { 69 return new Default(Default.TYPE_TO_STRING, rawKeyType); 70 } 71 if (rawKeyType == byte[].class) { 72 return new Default(Default.TYPE_BYTE_ARRAY, rawKeyType); 73 } 74 if (useDefault) { 75 // 19-Oct-2016, tatu: Used to just return DEFAULT_KEY_SERIALIZER but why not: 76 return new Default(Default.TYPE_TO_STRING, rawKeyType); 77 } 78 return null; 79 } 80 81 /** 82 * Method called if no specified key serializer was located; will return a 83 * "default" key serializer. 84 * 85 * @since 2.7 86 */ 87 @SuppressWarnings("unchecked") getFallbackKeySerializer(SerializationConfig config, Class<?> rawKeyType)88 public static JsonSerializer<Object> getFallbackKeySerializer(SerializationConfig config, 89 Class<?> rawKeyType) 90 { 91 if (rawKeyType != null) { 92 // 29-Sep-2015, tatu: Odd case here, of `Enum`, which we may get for `EnumMap`; not sure 93 // if that is a bug or feature. Regardless, it seems to require dynamic handling 94 // (compared to getting actual fully typed Enum). 95 // Note that this might even work from the earlier point, but let's play it safe for now 96 // 11-Aug-2016, tatu: Turns out we get this if `EnumMap` is the root value because 97 // then there is no static type 98 if (rawKeyType == Enum.class) { 99 return new Dynamic(); 100 } 101 // 29-Sep-2019, tatu: [databind#2457] can not use 'rawKeyType.isEnum()`, won't work 102 // for subtypes. 103 if (ClassUtil.isEnumType(rawKeyType)) { 104 return EnumKeySerializer.construct(rawKeyType, 105 EnumValues.constructFromName(config, (Class<Enum<?>>) rawKeyType)); 106 } 107 } 108 // 19-Oct-2016, tatu: Used to just return DEFAULT_KEY_SERIALIZER but why not: 109 return new Default(Default.TYPE_TO_STRING, rawKeyType); 110 } 111 112 /** 113 * @deprecated since 2.7 114 */ 115 @Deprecated getDefault()116 public static JsonSerializer<Object> getDefault() { 117 return DEFAULT_KEY_SERIALIZER; 118 } 119 120 /* 121 /********************************************************** 122 /* Standard implementations used 123 /********************************************************** 124 */ 125 126 /** 127 * This is a "chameleon" style multi-type key serializer for simple 128 * standard JDK types. 129 *<p> 130 * TODO: Should (but does not yet) support re-configuring format used for 131 * {@link java.util.Date} and {@link java.util.Calendar} key serializers, 132 * as well as alternative configuration of Enum key serializers. 133 */ 134 public static class Default extends StdSerializer<Object> { 135 final static int TYPE_DATE = 1; 136 final static int TYPE_CALENDAR = 2; 137 final static int TYPE_CLASS = 3; 138 final static int TYPE_ENUM = 4; 139 final static int TYPE_INTEGER = 5; // since 2.9 140 final static int TYPE_LONG = 6; // since 2.9 141 final static int TYPE_BYTE_ARRAY = 7; // since 2.9 142 final static int TYPE_TO_STRING = 8; 143 144 protected final int _typeId; 145 Default(int typeId, Class<?> type)146 public Default(int typeId, Class<?> type) { 147 super(type, false); 148 _typeId = typeId; 149 } 150 151 @Override serialize(Object value, JsonGenerator g, SerializerProvider provider)152 public void serialize(Object value, JsonGenerator g, SerializerProvider provider) throws IOException { 153 switch (_typeId) { 154 case TYPE_DATE: 155 provider.defaultSerializeDateKey((Date)value, g); 156 break; 157 case TYPE_CALENDAR: 158 provider.defaultSerializeDateKey(((Calendar) value).getTimeInMillis(), g); 159 break; 160 case TYPE_CLASS: 161 g.writeFieldName(((Class<?>)value).getName()); 162 break; 163 case TYPE_ENUM: 164 { 165 String key; 166 167 if (provider.isEnabled(SerializationFeature.WRITE_ENUMS_USING_TO_STRING)) { 168 key = value.toString(); 169 } else { 170 Enum<?> e = (Enum<?>) value; 171 // 14-Sep-2019, tatu: [databind#2129] Use this specific feature 172 if (provider.isEnabled(SerializationFeature.WRITE_ENUM_KEYS_USING_INDEX)) { 173 key = String.valueOf(e.ordinal()); 174 } else { 175 key = e.name(); 176 } 177 } 178 g.writeFieldName(key); 179 } 180 break; 181 case TYPE_INTEGER: 182 case TYPE_LONG: 183 g.writeFieldId(((Number) value).longValue()); 184 break; 185 case TYPE_BYTE_ARRAY: 186 { 187 String encoded = provider.getConfig().getBase64Variant().encode((byte[]) value); 188 g.writeFieldName(encoded); 189 } 190 break; 191 case TYPE_TO_STRING: 192 default: 193 g.writeFieldName(value.toString()); 194 } 195 } 196 } 197 198 /** 199 * Key serializer used when key type is not known statically, and actual key 200 * serializer needs to be dynamically located. 201 */ 202 public static class Dynamic extends StdSerializer<Object> 203 { 204 // Important: MUST be transient, to allow serialization of key serializer itself 205 protected transient PropertySerializerMap _dynamicSerializers; 206 Dynamic()207 public Dynamic() { 208 super(String.class, false); 209 _dynamicSerializers = PropertySerializerMap.emptyForProperties(); 210 } 211 readResolve()212 Object readResolve() { 213 // Since it's transient, and since JDK serialization by-passes ctor, need this: 214 _dynamicSerializers = PropertySerializerMap.emptyForProperties(); 215 return this; 216 } 217 218 @Override serialize(Object value, JsonGenerator g, SerializerProvider provider)219 public void serialize(Object value, JsonGenerator g, SerializerProvider provider) 220 throws IOException 221 { 222 Class<?> cls = value.getClass(); 223 PropertySerializerMap m = _dynamicSerializers; 224 JsonSerializer<Object> ser = m.serializerFor(cls); 225 if (ser == null) { 226 ser = _findAndAddDynamic(m, cls, provider); 227 } 228 ser.serialize(value, g, provider); 229 } 230 231 @Override acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)232 public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException { 233 visitStringFormat(visitor, typeHint); 234 } 235 _findAndAddDynamic(PropertySerializerMap map, Class<?> type, SerializerProvider provider)236 protected JsonSerializer<Object> _findAndAddDynamic(PropertySerializerMap map, 237 Class<?> type, SerializerProvider provider) throws JsonMappingException 238 { 239 // 27-Jun-2017, tatu: [databind#1679] Need to avoid StackOverflowError... 240 if (type == Object.class) { 241 // basically just need to call `toString()`, easiest way: 242 JsonSerializer<Object> ser = new Default(Default.TYPE_TO_STRING, type); 243 _dynamicSerializers = map.newWith(type, ser); 244 return ser; 245 } 246 PropertySerializerMap.SerializerAndMapResult result = 247 // null -> for now we won't keep ref or pass BeanProperty; could change 248 map.findAndAddKeySerializer(type, provider, null); 249 // did we get a new map of serializers? If so, start using it 250 if (map != result.map) { 251 _dynamicSerializers = result.map; 252 } 253 return result.serializer; 254 } 255 } 256 257 /** 258 * Simple and fast key serializer when keys are Strings. 259 */ 260 public static class StringKeySerializer extends StdSerializer<Object> 261 { StringKeySerializer()262 public StringKeySerializer() { super(String.class, false); } 263 264 @Override serialize(Object value, JsonGenerator g, SerializerProvider provider)265 public void serialize(Object value, JsonGenerator g, SerializerProvider provider) throws IOException { 266 g.writeFieldName((String) value); 267 } 268 } 269 270 /** 271 * Specialized instance to use for Enum keys, as per [databind#1322] 272 * 273 * @since 2.8 274 */ 275 public static class EnumKeySerializer extends StdSerializer<Object> 276 { 277 protected final EnumValues _values; 278 EnumKeySerializer(Class<?> enumType, EnumValues values)279 protected EnumKeySerializer(Class<?> enumType, EnumValues values) { 280 super(enumType, false); 281 _values = values; 282 } 283 construct(Class<?> enumType, EnumValues enumValues)284 public static EnumKeySerializer construct(Class<?> enumType, 285 EnumValues enumValues) 286 { 287 return new EnumKeySerializer(enumType, enumValues); 288 } 289 290 @Override serialize(Object value, JsonGenerator g, SerializerProvider serializers)291 public void serialize(Object value, JsonGenerator g, SerializerProvider serializers) 292 throws IOException 293 { 294 if (serializers.isEnabled(SerializationFeature.WRITE_ENUMS_USING_TO_STRING)) { 295 g.writeFieldName(value.toString()); 296 return; 297 } 298 Enum<?> en = (Enum<?>) value; 299 // 14-Sep-2019, tatu: [databind#2129] Use this specific feature 300 if (serializers.isEnabled(SerializationFeature.WRITE_ENUM_KEYS_USING_INDEX)) { 301 g.writeFieldName(String.valueOf(en.ordinal())); 302 return; 303 } 304 g.writeFieldName(_values.serializedValueFor(en)); 305 } 306 } 307 } 308