1 package com.fasterxml.jackson.databind.ser.std; 2 3 import java.io.IOException; 4 import java.lang.reflect.InvocationTargetException; 5 import java.lang.reflect.Type; 6 import java.util.Collection; 7 import java.util.IdentityHashMap; 8 import java.util.Map; 9 10 import com.fasterxml.jackson.annotation.JsonFormat; 11 import com.fasterxml.jackson.annotation.JsonInclude; 12 import com.fasterxml.jackson.core.*; 13 import com.fasterxml.jackson.core.JsonParser.NumberType; 14 import com.fasterxml.jackson.databind.*; 15 import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; 16 import com.fasterxml.jackson.databind.introspect.AnnotatedMember; 17 import com.fasterxml.jackson.databind.jsonFormatVisitors.*; 18 import com.fasterxml.jackson.databind.jsonschema.SchemaAware; 19 import com.fasterxml.jackson.databind.node.JsonNodeFactory; 20 import com.fasterxml.jackson.databind.node.ObjectNode; 21 import com.fasterxml.jackson.databind.ser.FilterProvider; 22 import com.fasterxml.jackson.databind.ser.PropertyFilter; 23 import com.fasterxml.jackson.databind.util.ClassUtil; 24 import com.fasterxml.jackson.databind.util.Converter; 25 26 /** 27 * Base class used by all standard serializers, and can also 28 * be used for custom serializers (in fact, this is the recommended 29 * base class to use). 30 * Provides convenience methods for implementing {@link SchemaAware} 31 */ 32 public abstract class StdSerializer<T> 33 extends JsonSerializer<T> 34 implements JsonFormatVisitable, SchemaAware, java.io.Serializable 35 { 36 private static final long serialVersionUID = 1L; 37 38 /** 39 * Key used for storing a lock object to prevent infinite recursion when 40 * constructing converting serializers. 41 * 42 * @since 2.9 43 */ 44 private final static Object KEY_CONTENT_CONVERTER_LOCK = new Object(); 45 46 /** 47 * Nominal type supported, usually declared type of 48 * property for which serializer is used. 49 */ 50 protected final Class<T> _handledType; 51 52 /* 53 /********************************************************** 54 /* Life-cycle 55 /********************************************************** 56 */ 57 StdSerializer(Class<T> t)58 protected StdSerializer(Class<T> t) { 59 _handledType = t; 60 } 61 62 @SuppressWarnings("unchecked") StdSerializer(JavaType type)63 protected StdSerializer(JavaType type) { 64 _handledType = (Class<T>) type.getRawClass(); 65 } 66 67 /** 68 * Alternate constructor that is (alas!) needed to work 69 * around kinks of generic type handling 70 */ 71 @SuppressWarnings("unchecked") StdSerializer(Class<?> t, boolean dummy)72 protected StdSerializer(Class<?> t, boolean dummy) { 73 _handledType = (Class<T>) t; 74 } 75 76 /** 77 * @since 2.6 78 */ 79 @SuppressWarnings("unchecked") StdSerializer(StdSerializer<?> src)80 protected StdSerializer(StdSerializer<?> src) { 81 _handledType = (Class<T>) src._handledType; 82 } 83 84 /* 85 /********************************************************** 86 /* Accessors 87 /********************************************************** 88 */ 89 90 @Override handledType()91 public Class<T> handledType() { return _handledType; } 92 93 /* 94 /********************************************************** 95 /* Serialization 96 /********************************************************** 97 */ 98 99 @Override serialize(T value, JsonGenerator gen, SerializerProvider provider)100 public abstract void serialize(T value, JsonGenerator gen, SerializerProvider provider) 101 throws IOException; 102 103 /* 104 /********************************************************** 105 /* Type introspection API, partial/default implementation 106 /********************************************************** 107 */ 108 109 /** 110 * Default implementation specifies no format. This behavior is usually 111 * overriden by custom serializers. 112 */ 113 @Override acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)114 public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) 115 throws JsonMappingException 116 { 117 visitor.expectAnyFormat(typeHint); 118 } 119 120 /** 121 * Default implementation simply claims type is "string"; usually 122 * overriden by custom serializers. 123 */ 124 @Override getSchema(SerializerProvider provider, Type typeHint)125 public JsonNode getSchema(SerializerProvider provider, Type typeHint) throws JsonMappingException 126 { 127 return createSchemaNode("string"); 128 } 129 130 /** 131 * Default implementation simply claims type is "string"; usually 132 * overriden by custom serializers. 133 */ 134 @Override getSchema(SerializerProvider provider, Type typeHint, boolean isOptional)135 public JsonNode getSchema(SerializerProvider provider, Type typeHint, boolean isOptional) 136 throws JsonMappingException 137 { 138 ObjectNode schema = (ObjectNode) getSchema(provider, typeHint); 139 if (!isOptional) { 140 schema.put("required", !isOptional); 141 } 142 return schema; 143 } 144 145 /* 146 /********************************************************** 147 /* Helper methods for JSON Schema generation 148 /********************************************************** 149 */ 150 createSchemaNode(String type)151 protected ObjectNode createSchemaNode(String type) 152 { 153 ObjectNode schema = JsonNodeFactory.instance.objectNode(); 154 schema.put("type", type); 155 return schema; 156 } 157 createSchemaNode(String type, boolean isOptional)158 protected ObjectNode createSchemaNode(String type, boolean isOptional) 159 { 160 ObjectNode schema = createSchemaNode(type); 161 if (!isOptional) { 162 schema.put("required", !isOptional); 163 } 164 return schema; 165 } 166 167 /** 168 * Helper method that calls necessary visit method(s) to indicate that the 169 * underlying JSON type is JSON String. 170 * 171 * @since 2.7 172 */ visitStringFormat(JsonFormatVisitorWrapper visitor, JavaType typeHint)173 protected void visitStringFormat(JsonFormatVisitorWrapper visitor, JavaType typeHint) 174 throws JsonMappingException { 175 /*JsonStringFormatVisitor v2 =*/ visitor.expectStringFormat(typeHint); 176 } 177 178 /** 179 * Helper method that calls necessary visit method(s) to indicate that the 180 * underlying JSON type is JSON String, but that there is a more refined 181 * logical type 182 * 183 * @since 2.7 184 */ visitStringFormat(JsonFormatVisitorWrapper visitor, JavaType typeHint, JsonValueFormat format)185 protected void visitStringFormat(JsonFormatVisitorWrapper visitor, JavaType typeHint, 186 JsonValueFormat format) 187 throws JsonMappingException 188 { 189 JsonStringFormatVisitor v2 = visitor.expectStringFormat(typeHint); 190 if (v2 != null) { 191 v2.format(format); 192 } 193 } 194 195 /** 196 * Helper method that calls necessary visit method(s) to indicate that the 197 * underlying JSON type is JSON Integer number. 198 * 199 * @since 2.7 200 */ visitIntFormat(JsonFormatVisitorWrapper visitor, JavaType typeHint, NumberType numberType)201 protected void visitIntFormat(JsonFormatVisitorWrapper visitor, JavaType typeHint, 202 NumberType numberType) 203 throws JsonMappingException 204 { 205 JsonIntegerFormatVisitor v2 = visitor.expectIntegerFormat(typeHint); 206 if (_neitherNull(v2, numberType)) { 207 v2.numberType(numberType); 208 } 209 } 210 211 /** 212 * Helper method that calls necessary visit method(s) to indicate that the 213 * underlying JSON type is JSON Integer number, but that there is also a further 214 * format restriction involved. 215 * 216 * @since 2.7 217 */ visitIntFormat(JsonFormatVisitorWrapper visitor, JavaType typeHint, NumberType numberType, JsonValueFormat format)218 protected void visitIntFormat(JsonFormatVisitorWrapper visitor, JavaType typeHint, 219 NumberType numberType, JsonValueFormat format) 220 throws JsonMappingException 221 { 222 JsonIntegerFormatVisitor v2 = visitor.expectIntegerFormat(typeHint); 223 if (v2 != null) { 224 if (numberType != null) { 225 v2.numberType(numberType); 226 } 227 if (format != null) { 228 v2.format(format); 229 } 230 } 231 } 232 233 /** 234 * Helper method that calls necessary visit method(s) to indicate that the 235 * underlying JSON type is a floating-point JSON number. 236 * 237 * @since 2.7 238 */ visitFloatFormat(JsonFormatVisitorWrapper visitor, JavaType typeHint, NumberType numberType)239 protected void visitFloatFormat(JsonFormatVisitorWrapper visitor, JavaType typeHint, 240 NumberType numberType) 241 throws JsonMappingException 242 { 243 JsonNumberFormatVisitor v2 = visitor.expectNumberFormat(typeHint); 244 if (v2 != null) { 245 v2.numberType(numberType); 246 } 247 } 248 249 /** 250 * @since 2.7 251 */ visitArrayFormat(JsonFormatVisitorWrapper visitor, JavaType typeHint, JsonSerializer<?> itemSerializer, JavaType itemType)252 protected void visitArrayFormat(JsonFormatVisitorWrapper visitor, JavaType typeHint, 253 JsonSerializer<?> itemSerializer, JavaType itemType) 254 throws JsonMappingException 255 { 256 JsonArrayFormatVisitor v2 = visitor.expectArrayFormat(typeHint); 257 if (_neitherNull(v2, itemSerializer)) { 258 v2.itemsFormat(itemSerializer, itemType); 259 } 260 } 261 262 /** 263 * @since 2.7 264 */ visitArrayFormat(JsonFormatVisitorWrapper visitor, JavaType typeHint, JsonFormatTypes itemType)265 protected void visitArrayFormat(JsonFormatVisitorWrapper visitor, JavaType typeHint, 266 JsonFormatTypes itemType) 267 throws JsonMappingException 268 { 269 JsonArrayFormatVisitor v2 = visitor.expectArrayFormat(typeHint); 270 if (v2 != null) { 271 v2.itemsFormat(itemType); 272 } 273 } 274 275 /* 276 /********************************************************** 277 /* Helper methods for exception handling 278 /********************************************************** 279 */ 280 281 /** 282 * Method that will modify caught exception (passed in as argument) 283 * as necessary to include reference information, and to ensure it 284 * is a subtype of {@link IOException}, or an unchecked exception. 285 *<p> 286 * Rules for wrapping and unwrapping are bit complicated; essentially: 287 *<ul> 288 * <li>Errors are to be passed as is (if uncovered via unwrapping) 289 * <li>"Plain" IOExceptions (ones that are not of type 290 * {@link JsonMappingException} are to be passed as is 291 *</ul> 292 */ wrapAndThrow(SerializerProvider provider, Throwable t, Object bean, String fieldName)293 public void wrapAndThrow(SerializerProvider provider, 294 Throwable t, Object bean, String fieldName) 295 throws IOException 296 { 297 /* 05-Mar-2009, tatu: But one nasty edge is when we get 298 * StackOverflow: usually due to infinite loop. But that 299 * usually gets hidden within an InvocationTargetException... 300 */ 301 while (t instanceof InvocationTargetException && t.getCause() != null) { 302 t = t.getCause(); 303 } 304 // Errors and "plain" to be passed as is 305 ClassUtil.throwIfError(t); 306 // Ditto for IOExceptions... except for mapping exceptions! 307 boolean wrap = (provider == null) || provider.isEnabled(SerializationFeature.WRAP_EXCEPTIONS); 308 if (t instanceof IOException) { 309 if (!wrap || !(t instanceof JsonMappingException)) { 310 throw (IOException) t; 311 } 312 } else if (!wrap) { 313 ClassUtil.throwIfRTE(t); 314 } 315 // Need to add reference information 316 throw JsonMappingException.wrapWithPath(t, bean, fieldName); 317 } 318 wrapAndThrow(SerializerProvider provider, Throwable t, Object bean, int index)319 public void wrapAndThrow(SerializerProvider provider, 320 Throwable t, Object bean, int index) 321 throws IOException 322 { 323 while (t instanceof InvocationTargetException && t.getCause() != null) { 324 t = t.getCause(); 325 } 326 // Errors are to be passed as is 327 ClassUtil.throwIfError(t); 328 // Ditto for IOExceptions... except for mapping exceptions! 329 boolean wrap = (provider == null) || provider.isEnabled(SerializationFeature.WRAP_EXCEPTIONS); 330 if (t instanceof IOException) { 331 if (!wrap || !(t instanceof JsonMappingException)) { 332 throw (IOException) t; 333 } 334 } else if (!wrap) { 335 ClassUtil.throwIfRTE(t); 336 } 337 // Need to add reference information 338 throw JsonMappingException.wrapWithPath(t, bean, index); 339 } 340 341 /* 342 /********************************************************** 343 /* Helper methods, accessing annotation-based configuration 344 /********************************************************** 345 */ 346 347 /** 348 * Helper method that can be used to see if specified property has annotation 349 * indicating that a converter is to be used for contained values (contents 350 * of structured types; array/List/Map values) 351 * 352 * @param existingSerializer (optional) configured content 353 * serializer if one already exists. 354 * 355 * @since 2.9 356 */ findContextualConvertingSerializer(SerializerProvider provider, BeanProperty property, JsonSerializer<?> existingSerializer)357 protected JsonSerializer<?> findContextualConvertingSerializer(SerializerProvider provider, 358 BeanProperty property, JsonSerializer<?> existingSerializer) 359 throws JsonMappingException 360 { 361 // 08-Dec-2016, tatu: to fix [databind#357], need to prevent recursive calls for 362 // same property 363 @SuppressWarnings("unchecked") 364 Map<Object,Object> conversions = (Map<Object,Object>) provider.getAttribute(KEY_CONTENT_CONVERTER_LOCK); 365 if (conversions != null) { 366 Object lock = conversions.get(property); 367 if (lock != null) { 368 return existingSerializer; 369 } 370 } else { 371 conversions = new IdentityHashMap<>(); 372 provider.setAttribute(KEY_CONTENT_CONVERTER_LOCK, conversions); 373 } 374 conversions.put(property, Boolean.TRUE); 375 try { 376 JsonSerializer<?> ser = findConvertingContentSerializer(provider, property, existingSerializer); 377 if (ser != null) { 378 return provider.handleSecondaryContextualization(ser, property); 379 } 380 } finally { 381 conversions.remove(property); 382 } 383 return existingSerializer; 384 } 385 386 /** 387 * @deprecated Since 2.9 use {link {@link #findContextualConvertingSerializer} instead 388 */ 389 @Deprecated findConvertingContentSerializer(SerializerProvider provider, BeanProperty prop, JsonSerializer<?> existingSerializer)390 protected JsonSerializer<?> findConvertingContentSerializer(SerializerProvider provider, 391 BeanProperty prop, JsonSerializer<?> existingSerializer) 392 throws JsonMappingException 393 { 394 final AnnotationIntrospector intr = provider.getAnnotationIntrospector(); 395 if (_neitherNull(intr, prop)) { 396 AnnotatedMember m = prop.getMember(); 397 if (m != null) { 398 Object convDef = intr.findSerializationContentConverter(m); 399 if (convDef != null) { 400 Converter<Object,Object> conv = provider.converterInstance(prop.getMember(), convDef); 401 JavaType delegateType = conv.getOutputType(provider.getTypeFactory()); 402 // [databind#731]: Should skip if nominally java.lang.Object 403 if ((existingSerializer == null) && !delegateType.isJavaLangObject()) { 404 existingSerializer = provider.findValueSerializer(delegateType); 405 } 406 return new StdDelegatingSerializer(conv, delegateType, existingSerializer); 407 } 408 } 409 } 410 return existingSerializer; 411 } 412 413 /** 414 * Helper method used to locate filter that is needed, based on filter id 415 * this serializer was constructed with. 416 * 417 * @since 2.3 418 */ findPropertyFilter(SerializerProvider provider, Object filterId, Object valueToFilter)419 protected PropertyFilter findPropertyFilter(SerializerProvider provider, 420 Object filterId, Object valueToFilter) 421 throws JsonMappingException 422 { 423 FilterProvider filters = provider.getFilterProvider(); 424 // Not ok to miss the provider, if a filter is declared to be needed. 425 if (filters == null) { 426 provider.reportBadDefinition(handledType(), 427 "Cannot resolve PropertyFilter with id '"+filterId+"'; no FilterProvider configured"); 428 } 429 // But whether unknown ids are ok just depends on filter provider; if we get null that's fine 430 return filters.findPropertyFilter(filterId, valueToFilter); 431 } 432 433 /** 434 * Helper method that may be used to find if this deserializer has specific 435 * {@link JsonFormat} settings, either via property, or through type-specific 436 * defaulting. 437 * 438 * @param typeForDefaults Type (erased) used for finding default format settings, if any 439 * 440 * @since 2.7 441 */ findFormatOverrides(SerializerProvider provider, BeanProperty prop, Class<?> typeForDefaults)442 protected JsonFormat.Value findFormatOverrides(SerializerProvider provider, 443 BeanProperty prop, Class<?> typeForDefaults) 444 { 445 if (prop != null) { 446 return prop.findPropertyFormat(provider.getConfig(), typeForDefaults); 447 } 448 // even without property or AnnotationIntrospector, may have type-specific defaults 449 return provider.getDefaultPropertyFormat(typeForDefaults); 450 } 451 452 /** 453 * Convenience method that uses {@link #findFormatOverrides} to find possible 454 * defaults and/of overrides, and then calls <code>JsonFormat.Value.getFeature(...)</code> 455 * to find whether that feature has been specifically marked as enabled or disabled. 456 * 457 * @param typeForDefaults Type (erased) used for finding default format settings, if any 458 * 459 * @since 2.7 460 */ findFormatFeature(SerializerProvider provider, BeanProperty prop, Class<?> typeForDefaults, JsonFormat.Feature feat)461 protected Boolean findFormatFeature(SerializerProvider provider, 462 BeanProperty prop, Class<?> typeForDefaults, JsonFormat.Feature feat) 463 { 464 JsonFormat.Value format = findFormatOverrides(provider, prop, typeForDefaults); 465 if (format != null) { 466 return format.getFeature(feat); 467 } 468 return null; 469 } 470 471 /** 472 * @since 2.8 473 */ findIncludeOverrides(SerializerProvider provider, BeanProperty prop, Class<?> typeForDefaults)474 protected JsonInclude.Value findIncludeOverrides(SerializerProvider provider, 475 BeanProperty prop, Class<?> typeForDefaults) 476 { 477 if (prop != null) { 478 return prop.findPropertyInclusion(provider.getConfig(), typeForDefaults); 479 } 480 // even without property or AnnotationIntrospector, may have type-specific defaults 481 return provider.getDefaultPropertyInclusion(typeForDefaults); 482 } 483 484 /** 485 * Convenience method for finding out possibly configured content value serializer. 486 * 487 * @since 2.7.4 488 */ findAnnotatedContentSerializer(SerializerProvider serializers, BeanProperty property)489 protected JsonSerializer<?> findAnnotatedContentSerializer(SerializerProvider serializers, 490 BeanProperty property) 491 throws JsonMappingException 492 { 493 if (property != null) { 494 // First: if we have a property, may have property-annotation overrides 495 AnnotatedMember m = property.getMember(); 496 final AnnotationIntrospector intr = serializers.getAnnotationIntrospector(); 497 if (m != null) { 498 Object serDef = intr.findContentSerializer(m); 499 if (serDef != null) { 500 return serializers.serializerInstance(m, serDef); 501 } 502 } 503 } 504 return null; 505 } 506 507 /* 508 /********************************************************** 509 /* Helper methods, other 510 /********************************************************** 511 */ 512 513 /** 514 * Method that can be called to determine if given serializer is the default 515 * serializer Jackson uses; as opposed to a custom serializer installed by 516 * a module or calling application. Determination is done using 517 * {@link JacksonStdImpl} annotation on serializer class. 518 */ isDefaultSerializer(JsonSerializer<?> serializer)519 protected boolean isDefaultSerializer(JsonSerializer<?> serializer) { 520 return ClassUtil.isJacksonStdImpl(serializer); 521 } 522 523 /** 524 * @since 2.9 525 */ _neitherNull(Object a, Object b)526 protected final static boolean _neitherNull(Object a, Object b) { 527 return (a != null) && (b != null); 528 } 529 530 /** 531 * @since 2.9 532 */ _nonEmpty(Collection<?> c)533 protected final static boolean _nonEmpty(Collection<?> c) { 534 return (c != null) && !c.isEmpty(); 535 } 536 } 537