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
}
}