All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.fasterxml.jackson.module.scala.ser.OptionSerializerModule.scala Maven / Gradle / Ivy

The newest version!
package com.fasterxml.jackson
package module.scala
package ser

import java.lang.reflect.Type
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.databind._
import com.fasterxml.jackson.databind.`type`.ReferenceType
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper
import com.fasterxml.jackson.databind.jsonschema.{JsonSchema, SchemaAware}
import com.fasterxml.jackson.databind.jsontype.TypeSerializer
import com.fasterxml.jackson.databind.ser.impl.{PropertySerializerMap, UnknownSerializer}
import com.fasterxml.jackson.databind.ser.std.StdSerializer
import com.fasterxml.jackson.databind.ser.{ContextualSerializer, Serializers}
import com.fasterxml.jackson.databind.util.NameTransformer
import com.fasterxml.jackson.module.scala.modifiers.OptionTypeModifierModule
import com.fasterxml.jackson.module.scala.util.Implicits._

object OptionSerializer {
  def useStatic(provider: SerializerProvider, property: Option[BeanProperty], referredType: Option[JavaType]): Boolean = {
    if (referredType.isEmpty) return false
    // First: no serializer for `Object.class`, must be dynamic
    if (referredType.get.isJavaLangObject) return false
    // but if type is final, might as well fetch
    if (referredType.get.isFinal) return true
    // also: if indicated by typing, should be considered static
    if (referredType.get.useStaticType()) return true
    // if neither, maybe explicit annotation?
    for (
      ann <- property.flatMap(p => Option(p.getMember));
      intr <- Option(provider.getAnnotationIntrospector)
    ) {
      val typing = intr.findSerializationTyping(ann)
      if (typing == JsonSerialize.Typing.STATIC) return true
      if (typing == JsonSerialize.Typing.DYNAMIC) return false
    }
    // and finally, may be forced by global static typing (unlikely...)
    provider.isEnabled(MapperFeature.USE_STATIC_TYPING)
  }

  def findSerializer(provider: SerializerProvider, typ: Class[_], prop: Option[BeanProperty]): JsonSerializer[AnyRef] = {
    // Important: ask for TYPED serializer, in case polymorphic handling is needed!
    provider.findTypedValueSerializer(typ, true, prop.orNull)
  }

  def findSerializer(provider: SerializerProvider, typ: JavaType, prop: Option[BeanProperty]): JsonSerializer[AnyRef] = {
    // Important: ask for TYPED serializer, in case polymorphic handling is needed!
    provider.findTypedValueSerializer(typ, true, prop.orNull)
  }

  def hasContentTypeAnnotation(provider: SerializerProvider, property: BeanProperty): Boolean = {
    val intr = provider.getAnnotationIntrospector
    if (property == null || intr == null) return false
    intr.refineSerializationType(provider.getConfig, property.getMember, property.getType) != null
  }
}

private class OptionSerializer(referredType: JavaType,
                               property: Option[BeanProperty],
                               valueTypeSerializer: Option[TypeSerializer],
                               valueSerializer: Option[JsonSerializer[AnyRef]],
                               contentInclusion: Option[JsonInclude.Include],
                               unwrapper: Option[NameTransformer],
                               var dynamicSerializers: PropertySerializerMap = PropertySerializerMap.emptyForProperties())
  extends StdSerializer[Option[AnyRef]](referredType)
    with ContextualSerializer
    with SchemaAware {

  import OptionSerializer._

  override def unwrappingSerializer(transformer: NameTransformer): JsonSerializer[Option[AnyRef]] = {
    val ser = valueSerializer.map(_.unwrappingSerializer(transformer))
    val unt = unwrapper.map(NameTransformer.chainedTransformer(transformer, _)).getOrElse(transformer)
    withResolved(property, valueTypeSerializer, ser, Option(unt), contentInclusion)
  }

  protected[this] def withResolved(prop: Option[BeanProperty], vts: Option[TypeSerializer],
                                   valueSer: Option[JsonSerializer[AnyRef]], unt: Option[NameTransformer],
                                   contentIncl: Option[JsonInclude.Include]): OptionSerializer = {
    if (prop == property && vts == valueTypeSerializer && valueSer == valueSerializer &&
      contentIncl == contentInclusion && unt == unwrapper) this
    else new OptionSerializer(referredType, prop, vts, valueSer, contentIncl, unt, dynamicSerializers)
  }

  override def createContextual(prov: SerializerProvider, prop: BeanProperty): JsonSerializer[_] = {
    val propOpt = Option(prop)

    val vts = valueTypeSerializer.optMap(_.forProperty(prop))
    var ser = for (
      prop <- propOpt;
      member <- Option(prop.getMember);
      serDef <- Option(prov.getAnnotationIntrospector.findContentSerializer(member))
    ) yield prov.serializerInstance(member, serDef)
    ser = ser.orElse(valueSerializer).map(prov.handlePrimaryContextualization(_, prop)).asInstanceOf[Option[JsonSerializer[AnyRef]]]
    ser = Option(findConvertingContentSerializer(prov, prop, ser.orNull).asInstanceOf[JsonSerializer[AnyRef]])
    ser = ser match {
      case None => if (hasContentTypeAnnotation(prov, prop)) {
        Option(prov.findValueSerializer(referredType, prop)).filterNot(_.isInstanceOf[UnknownSerializer])
      } else None
      case Some(s) => Option(prov.handlePrimaryContextualization(s, prop).asInstanceOf[JsonSerializer[AnyRef]])
    }

    // A few conditions needed to be able to fetch serializer here:
    if (ser.isEmpty && useStatic(prov, propOpt, Option(referredType))) {
      ser = Option(findSerializer(prov, referredType, propOpt))
    }
    // Also: may want to have more refined exclusion based on referenced value
    val newIncl = propOpt match {
      case None => contentInclusion
      case Some(p) =>
        val pinc = p.findPropertyInclusion(prov.getConfig, classOf[Option[AnyRef]])
        val incl = pinc.getContentInclusion
        if (incl != JsonInclude.Include.USE_DEFAULTS) {
          Some(incl)
        } else contentInclusion
    }
    withResolved(propOpt, vts, ser, unwrapper, newIncl)
  }

  override def isEmpty(provider: SerializerProvider, value: Option[AnyRef]): Boolean = {
    if (value == null || value.isEmpty) return true
    if (contentInclusion.isEmpty) return false
    val contents = value.get
    valueSerializer
      .getOrElse(findCachedSerializer(provider, contents.getClass))
      .isEmpty(provider, contents)
  }

  override def isUnwrappingSerializer: Boolean = unwrapper.isDefined

  override def serialize(opt: Option[AnyRef], gen: JsonGenerator, provider: SerializerProvider): Unit = {
    if (opt.isEmpty) {
      if (unwrapper.isEmpty) {
        provider.defaultSerializeNull(gen)
      }
      return
    }

    val value = opt.get
    val ser = valueSerializer.getOrElse(findCachedSerializer(provider, value.getClass))
    valueTypeSerializer match {
      case Some(vts) => ser.serializeWithType(value, gen, provider, vts)
      case None => ser.serialize(value, gen, provider)
    }
  }

  override def serializeWithType(opt: Option[AnyRef], gen: JsonGenerator, provider: SerializerProvider, typeSer: TypeSerializer): Unit = {
    if (opt.isEmpty) {
      if (unwrapper.isEmpty) {
        provider.defaultSerializeNull(gen)
      }
      return
    }
    // Otherwise apply type-prefix/suffix, then std serialize:
    typeSer.writeTypePrefixForScalar(opt, gen, classOf[Option[_]])
    serialize(opt, gen, provider)
    typeSer.writeTypeSuffixForScalar(opt, gen)
  }

  override def getSchema(provider: SerializerProvider, typeHint: Type): JsonNode =
    getSchema(provider, typeHint, isOptional = true)

  override def getSchema(provider: SerializerProvider, typeHint: Type, isOptional: Boolean): JsonNode = {
    val contentSerializer = valueSerializer.getOrElse {
      val javaType = provider.constructType(typeHint)
      val componentType = javaType.getContentType
      provider.findTypedValueSerializer(componentType, true, property.orNull)
    }
    contentSerializer match {
      case cs: SchemaAware => cs.getSchema(provider, contentSerializer.handledType(), isOptional)
      case _ => JsonSchema.getDefaultSchemaNode
    }
  }

  override def acceptJsonFormatVisitor(visitor: JsonFormatVisitorWrapper, typeHint: JavaType): Unit = {
    var ser = valueSerializer.getOrElse(findSerializer(visitor.getProvider, referredType, property))
    ser = unwrapper.map(ser.unwrappingSerializer).getOrElse(ser)
    ser.acceptJsonFormatVisitor(visitor, referredType)
  }

  protected[this] def findCachedSerializer(prov: SerializerProvider, typ: Class[_]): JsonSerializer[AnyRef] = {
    var ser = dynamicSerializers.serializerFor(typ)
    if (ser == null) {
      ser = findSerializer(prov, typ, property)
      ser = unwrapper.map(ser.unwrappingSerializer).getOrElse(ser)
      dynamicSerializers = dynamicSerializers.newWith(typ, ser)
    }
    ser
  }
}

private object OptionSerializerResolver extends Serializers.Base {

  private val OPTION = classOf[Option[_]]

  override def findReferenceSerializer(config: SerializationConfig,
                                       refType: ReferenceType,
                                       beanDesc: BeanDescription,
                                       contentTypeSerializer: TypeSerializer,
                                       contentValueSerializer: JsonSerializer[AnyRef]): JsonSerializer[_] = {
    if (!OPTION.isAssignableFrom(refType.getRawClass)) return null
    new OptionSerializer(refType.getReferencedType, property = None,
      valueTypeSerializer = Option(contentTypeSerializer).orElse(Option(refType.getTypeHandler[TypeSerializer])),
      valueSerializer = Option(contentValueSerializer).orElse(Option(refType.getValueHandler[JsonSerializer[AnyRef]])),
      contentInclusion = None, unwrapper = None)
  }
}

trait OptionSerializerModule extends OptionTypeModifierModule {
  this += { ctx =>
    ctx addSerializers OptionSerializerResolver
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy