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

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

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

import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.core.{JsonGenerator, JsonToken}
import com.fasterxml.jackson.databind._
import com.fasterxml.jackson.databind.`type`.ReferenceType
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.module.scala.modifiers.EitherTypeModifierModule
import com.fasterxml.jackson.module.scala.util.Implicits._

import scala.language.existentials

private case class EitherDetails(typ: Option[JavaType],
                                 valueTypeSerializer: Option[TypeSerializer],
                                 valueSerializer: Option[JsonSerializer[AnyRef]]) {
  def withHandlers(vtsOpt: Option[TypeSerializer], serOpt: Option[JsonSerializer[AnyRef]]): EitherDetails = {
    var newType = typ
    for (vts <- vtsOpt) {
      newType = newType.map(_.withTypeHandler(vts))
    }
    for (ser <- serOpt) {
      newType = newType.map(_.withValueHandler(ser))
    }
    copy(typ = newType)
  }
}

private class EitherSerializer(left: EitherDetails,
                               right: EitherDetails,
                               property: Option[BeanProperty],
                               contentInclusion: Option[JsonInclude.Include] = None,
                               var dynamicSerializers: PropertySerializerMap = PropertySerializerMap.emptyForProperties())
  extends StdSerializer[Either[AnyRef, AnyRef]](classOf[Either[AnyRef, AnyRef]])
    with ContextualSerializer {

  import com.fasterxml.jackson.module.scala.ser.OptionSerializer._

  protected[this] def withResolved(prop: Option[BeanProperty], newLeft: EitherDetails, newRight: EitherDetails,
                                   contentIncl: Option[JsonInclude.Include]): EitherSerializer = {
    if (prop == property && left == newLeft && right == newRight && contentIncl == contentInclusion) this
    else new EitherSerializer(newLeft, newRight, prop, contentIncl, dynamicSerializers)
  }

  protected[this] def createContextualDetails(prov: SerializerProvider,
                                              prop: BeanProperty,
                                              details: EitherDetails): EitherDetails = {
    val vts = details.valueTypeSerializer.optMap(_.forProperty(prop))
    var ser = for (
      prop <- Option(prop);
      member <- Option(prop.getMember);
      serDef <- Option(prov.getAnnotationIntrospector.findContentSerializer(member))
    ) yield prov.serializerInstance(member, serDef)
    ser = ser
      .orElse(details.valueSerializer)
      .map(prov.handlePrimaryContextualization(_, prop))
      .asInstanceOf[Option[JsonSerializer[AnyRef]]]
    ser = Option(findContextualConvertingSerializer(prov, prop, ser.orNull))
      .asInstanceOf[Option[JsonSerializer[AnyRef]]]
    ser = ser match {
      case None => if (details.typ.isDefined && hasContentTypeAnnotation(prov, prop)) {
        Option(prov.findValueSerializer(details.typ.get, 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, Option(prop), details.typ)) {
      ser = Option(findSerializer(prov, details.typ.orNull, Option(prop)))
    }

    details.copy(valueTypeSerializer = vts, valueSerializer = ser)
  }

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

    val newLeft = createContextualDetails(prov, prop, left)
    val newRight = createContextualDetails(prov, prop, right)

    // 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, newLeft, newRight, newIncl)
  }

  override def serialize(value: Either[AnyRef, AnyRef], jgen: JsonGenerator, provider: SerializerProvider): Unit = {
    serialize(value, jgen, provider, None)
  }

  def serialize(value: Either[AnyRef, AnyRef], jgen: JsonGenerator, provider: SerializerProvider, vts: Option[TypeSerializer]): Unit = {
    val (field, content, details) = value match {
      case Left(c) => ("l", c, left)
      case Right(c) => ("r", c, right)
    }
    jgen.writeStartObject()
    jgen.writeFieldName(field)
    if (content == null) {
      provider.defaultSerializeNull(jgen)
    } else {
      val ser = details.valueSerializer.getOrElse(findCachedSerializer(provider, content.getClass))
      vts.orElse(details.valueTypeSerializer) match {
        case Some(vts) => ser.serializeWithType(content, jgen, provider, vts)
        case None => ser.serialize(content, jgen, provider)
      }
    }
    jgen.writeEndObject()
  }

  override def serializeWithType(value: Either[AnyRef, AnyRef], jgen: JsonGenerator, provider: SerializerProvider, typeSer: TypeSerializer): Unit = {
    if (value == null) {
      provider.defaultSerializeNull(jgen)
    } else {
      // Otherwise apply type-prefix/suffix, then std serialize:
      typeSer.writeTypePrefix(jgen, typeSer.typeId(value, JsonToken.START_OBJECT))
      serialize(value, jgen, provider, Some(typeSer))
      typeSer.writeTypeSuffix(jgen, typeSer.typeId(value, JsonToken.END_OBJECT))
    }
  }

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

private object EitherSerializerResolver extends Serializers.Base {

  private val EITHER = classOf[Either[AnyRef, AnyRef]]
  private val LEFT = classOf[Left[AnyRef, AnyRef]]
  private val RIGHT = classOf[Right[AnyRef, AnyRef]]

  override def findReferenceSerializer(config: SerializationConfig,
                                       refType: ReferenceType,
                                       beanDesc: BeanDescription,
                                       contentTypeSerializer: TypeSerializer,
                                       contentValueSerializer: JsonSerializer[AnyRef]): JsonSerializer[_] = {
    if (!EITHER.isAssignableFrom(refType.getRawClass)) None.orNull
    else {
      val javaType = if (LEFT.isAssignableFrom(refType.getRawClass) || RIGHT.isAssignableFrom(refType.getRawClass)) {
        refType.getReferencedType.getSuperClass
      } else refType.getReferencedType

      val leftType = javaType.containedType(0)
      val rightType = javaType.containedType(1)

      val typeSer = Option(contentTypeSerializer).orElse(Option(javaType.getTypeHandler[TypeSerializer]))
      val valSer = Option(contentValueSerializer).orElse(Option(javaType.getValueHandler[JsonSerializer[AnyRef]]))

      val left = EitherDetails(Option(leftType), typeSer, valSer)
      val right = EitherDetails(Option(rightType), typeSer, valSer)

      new EitherSerializer(left.withHandlers(typeSer, valSer), right.withHandlers(typeSer, valSer), None)
    }
  }
}

trait EitherSerializerModule extends EitherTypeModifierModule {
  this += (_ addSerializers EitherSerializerResolver)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy