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