def builder: ScalaObjectMapper.Builder = Builder()

in util-jackson/src/main/scala/com/twitter/util/jackson/ScalaObjectMapper.scala [181:410]


  def builder: ScalaObjectMapper.Builder = Builder()

  /**
   * A Builder for creating a new [[ScalaObjectMapper]]. E.g., to build a new instance of
   * a [[ScalaObjectMapper]].
   *
   * For example,
   * {{{
   *   ScalaObjectMapper.builder
   *     .withPropertyNamingStrategy(new PropertyNamingStrategies.UpperCamelCaseStrategy)
   *     .withNumbersAsStrings(true)
   *     .withAdditionalJacksonModules(...)
   *     .objectMapper
   * }}}
   *
   * or
   *
   * {{{
   *   val builder =
   *     ScalaObjectMapper.builder
   *       .withPropertyNamingStrategy(new PropertyNamingStrategies.UpperCamelCaseStrategy)
   *       .withNumbersAsStrings(true)
   *       .withAdditionalJacksonModules(...)
   *
   *     val mapper = builder.objectMapper
   *     val camelCaseMapper = builder.camelCaseObjectMapper
   * }}}
   */
  case class Builder private[jackson] (
    propertyNamingStrategy: PropertyNamingStrategy = DefaultPropertyNamingStrategy,
    numbersAsStrings: Boolean = DefaultNumbersAsStrings,
    serializationInclude: Include = DefaultSerializationInclude,
    serializationConfig: Map[SerializationFeature, Boolean] = DefaultSerializationConfig,
    deserializationConfig: Map[DeserializationFeature, Boolean] = DefaultDeserializationConfig,
    defaultJacksonModules: Seq[Module] = DefaultJacksonModules,
    validator: Option[ScalaValidator] = Some(DefaultValidator),
    additionalJacksonModules: Seq[Module] = DefaultAdditionalJacksonModules,
    additionalMapperConfigurationFns: Seq[JacksonObjectMapper => Unit] = Seq.empty,
    validation: Boolean = DefaultValidation) {

    /* Public */

    /** Create a new [[ScalaObjectMapper]] from this [[Builder]]. */
    final def objectMapper: ScalaObjectMapper =
      new ScalaObjectMapper(jacksonScalaObjectMapper)

    /** Create a new [[ScalaObjectMapper]] from this [[Builder]] using the given [[JsonFactory]]. */
    final def objectMapper[F <: JsonFactory](factory: F): ScalaObjectMapper =
      new ScalaObjectMapper(jacksonScalaObjectMapper(factory))

    /**
     * Create a new [[ScalaObjectMapper]] explicitly configured to serialize and deserialize
     * YAML from this [[Builder]].
     *
     * @note the used [[PropertyNamingStrategy]] is defined by the current [[Builder]] configuration.
     */
    final def yamlObjectMapper: ScalaObjectMapper =
      new ScalaObjectMapper(
        configureJacksonScalaObjectMapper(new YAMLFactoryBuilder(new YAMLFactory())))

    /**
     * Creates a new [[ScalaObjectMapper]] explicitly configured with
     * [[PropertyNamingStrategies.LOWER_CAMEL_CASE]] as a `PropertyNamingStrategy`.
     */
    final def camelCaseObjectMapper: ScalaObjectMapper =
      ScalaObjectMapper.camelCaseObjectMapper(jacksonScalaObjectMapper)

    /**
     * Creates a new [[ScalaObjectMapper]] explicitly configured with
     * [[PropertyNamingStrategies.SNAKE_CASE]] as a `PropertyNamingStrategy`.
     */
    final def snakeCaseObjectMapper: ScalaObjectMapper =
      ScalaObjectMapper.snakeCaseObjectMapper(jacksonScalaObjectMapper)

    /* Builder Methods */

    /**
     * Configure a [[PropertyNamingStrategy]] for this [[Builder]].
     * @note the default is [[PropertyNamingStrategies.SNAKE_CASE]]
     * @see [[ScalaObjectMapper.DefaultPropertyNamingStrategy]]
     */
    final def withPropertyNamingStrategy(propertyNamingStrategy: PropertyNamingStrategy): Builder =
      this.copy(propertyNamingStrategy = propertyNamingStrategy)

    /**
     * Enable the [[JsonWriteFeature.WRITE_NUMBERS_AS_STRINGS]] for this [[Builder]].
     * @note the default is false.
     */
    final def withNumbersAsStrings(numbersAsStrings: Boolean): Builder =
      this.copy(numbersAsStrings = numbersAsStrings)

    /**
     * Configure a [[JsonInclude.Include]] for serialization for this [[Builder]].
     * @note the default is [[JsonInclude.Include.NON_ABSENT]]
     * @see [[ScalaObjectMapper.DefaultSerializationInclude]]
     */
    final def withSerializationInclude(serializationInclude: Include): Builder =
      this.copy(serializationInclude = serializationInclude)

    /**
     * Set the serialization configuration for this [[Builder]] as a `Map` of `SerializationFeature`
     * to `Boolean` (enabled).
     * @note the default is described by [[ScalaObjectMapper.DefaultSerializationConfig]].
     * @see [[ScalaObjectMapper.DefaultSerializationConfig]]
     */
    final def withSerializationConfig(
      serializationConfig: Map[SerializationFeature, Boolean]
    ): Builder =
      this.copy(serializationConfig = serializationConfig)

    /**
     * Set the deserialization configuration for this [[Builder]] as a `Map` of `DeserializationFeature`
     * to `Boolean` (enabled).
     * @note this overwrites the default deserialization configuration of this [[Builder]].
     * @note the default is described by [[ScalaObjectMapper.DefaultDeserializationConfig]].
     * @see [[ScalaObjectMapper.DefaultDeserializationConfig]]
     */
    final def withDeserializationConfig(
      deserializationConfig: Map[DeserializationFeature, Boolean]
    ): Builder =
      this.copy(deserializationConfig = deserializationConfig)

    /**
     * Configure a [[ScalaValidator]] for this [[Builder]]
     * @see [[ScalaObjectMapper.DefaultValidator]]
     *
     * @note If you pass `withNoValidation` to the builder all case class validations will be
     *       bypassed, regardless of the `withValidator` configuration.
     */
    final def withValidator(validator: ScalaValidator): Builder =
      this.copy(validator = Some(validator))

    /**
     * Configure the list of additional Jackson [[Module]]s for this [[Builder]].
     * @note this will overwrite (not append) the list additional Jackson [[Module]]s of this [[Builder]].
     */
    final def withAdditionalJacksonModules(additionalJacksonModules: Seq[Module]): Builder =
      this.copy(additionalJacksonModules = additionalJacksonModules)

    /**
     * Configure additional [[JacksonObjectMapper]] functionality for the underlying mapper of this [[Builder]].
     * @note this will overwrite any previously set function.
     */
    final def withAdditionalMapperConfigurationFn(mapperFn: JacksonObjectMapper => Unit): Builder =
      this
        .copy(additionalMapperConfigurationFns = this.additionalMapperConfigurationFns :+ mapperFn)

    /** Method to allow changing of the default Jackson Modules for use from the `ScalaObjectMapperModule` */
    private[twitter] final def withDefaultJacksonModules(
      defaultJacksonModules: Seq[Module]
    ): Builder =
      this.copy(defaultJacksonModules = defaultJacksonModules)

    /**
     * Disable case class validation during case class deserialization
     *
     * @see [[ScalaObjectMapper.DefaultValidation]]
     * @note If you pass `withNoValidation` to the builder all case class validations will be
     *       bypassed, regardless of the `withValidator` configuration.
     */
    final def withNoValidation: Builder =
      this.copy(validation = false)

    /* Private */

    private[this] def defaultMapperConfiguration(mapper: JacksonObjectMapper): Unit = {
      /* Serialization Config */
      mapper.setDefaultPropertyInclusion(
        JsonInclude.Value.construct(serializationInclude, serializationInclude))
      mapper
        .configOverride(classOf[Option[_]])
        .setIncludeAsProperty(JsonInclude.Value.construct(serializationInclude, Include.ALWAYS))
      for ((feature, state) <- serializationConfig) {
        mapper.configure(feature, state)
      }

      /* Deserialization Config */
      for ((feature, state) <- deserializationConfig) {
        mapper.configure(feature, state)
      }
    }

    /** Order is important: default + case class module + any additional */
    private[this] def jacksonModules: Seq[Module] = {
      this.defaultJacksonModules ++
        Seq(new CaseClassJacksonModule(if (this.validation) this.validator else None)) ++
        this.additionalJacksonModules
    }

    private[this] final def jacksonScalaObjectMapper: JacksonScalaObjectMapperType =
      configureJacksonScalaObjectMapper(new JsonFactoryBuilder)

    private[this] final def jacksonScalaObjectMapper[F <: JsonFactory](
      jsonFactory: F
    ): JacksonScalaObjectMapperType = configureJacksonScalaObjectMapper(jsonFactory)

    private[this] final def configureJacksonScalaObjectMapper[
      F <: JsonFactory,
      B <: TSFBuilder[F, B]
    ](
      builder: TSFBuilder[F, B]
    ): JacksonScalaObjectMapperType = configureJacksonScalaObjectMapper(builder.build())

    private[jackson] final def configureJacksonScalaObjectMapper(
      factory: JsonFactory
    ): JacksonScalaObjectMapperType =
      configureJacksonScalaObjectMapper(
        new JacksonObjectMapper(factory) with JacksonScalaObjectMapper)

    private[jackson] final def configureJacksonScalaObjectMapper(
      underlying: JacksonScalaObjectMapperType
    ): JacksonScalaObjectMapperType = {
      if (this.numbersAsStrings) {
        underlying.enable(JsonWriteFeature.WRITE_NUMBERS_AS_STRINGS.mappedFeature())
      }

      this.defaultMapperConfiguration(underlying)
      this.additionalMapperConfigurationFns.foreach(_(underlying))

      underlying.setPropertyNamingStrategy(this.propertyNamingStrategy)
      // Block use of a set of "unsafe" base types such as java.lang.Object
      // to prevent exploitation of Remote Code Execution (RCE) vulnerability
      // This line can be removed when this feature is enabled by default in Jackson 3
      underlying.enable(MapperFeature.BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES)

      this.jacksonModules.foreach(underlying.registerModule)

      underlying
    }
  }