Jackson 2.10.0 (#27814)

* Update Jackson to 2.10.0

* Support configuration for new features introduced in Jackson 2.10

* WRITE_DURATIONS_AS_TIMESTAMPS is the new config used to serialize durations

Previously WRITE_DATES_AS_TIMESTAMPS was used for both date/time and duration, but
in Jackson 2.10 WRITE_DURATIONS_AS_TIMESTAMPS is used for durations, so it needs to
be configured consistently with WRITE_DATES_AS_TIMESTAMPS.
This commit is contained in:
Marcos Pereira 2019-10-02 22:58:11 -04:00 committed by Helena Edelson
parent 619f821e8d
commit 92b9db5858
7 changed files with 288 additions and 55 deletions

View file

@ -202,7 +202,7 @@ class JacksonSerializationBench {
}
serialization.jackson {
compress-larger-than = 100000 b
serialization-features {
#WRITE_DATES_AS_TIMESTAMPS = off
}

View file

@ -394,7 +394,7 @@ Jackson are used aside from the the following that are changed in Akka's default
### Date/time format
`WRITE_DATES_AS_TIMESTAMPS` is by default disabled, which means that date/time fields are serialized in
`WRITE_DATES_AS_TIMESTAMPS` and `WRITE_DURATIONS_AS_TIMESTAMPS` are by default disabled, which means that date/time fields are serialized in
ISO-8601 (rfc3339) `yyyy-MM-dd'T'HH:mm:ss.SSSZ` format instead of numeric arrays. This is better for
interoperability but it is slower. If you don't need the ISO format for interoperability with external systems
you can change the following configuration for better performance of date/time fields.

View file

@ -55,6 +55,7 @@ akka.serialization.jackson {
# For interoperability it's better to use the ISO format, i.e. WRITE_DATES_AS_TIMESTAMPS=off,
# but WRITE_DATES_AS_TIMESTAMPS=on has better performance.
WRITE_DATES_AS_TIMESTAMPS = off
WRITE_DURATIONS_AS_TIMESTAMPS = off
}
# Configuration of the ObjectMapper deserialization features.
@ -94,6 +95,46 @@ akka.serialization.jackson {
# }
json-generator-features {}
# Configuration of the JsonFactory StreamReadFeature.
# See com.fasterxml.jackson.core.StreamReadFeature
# Enum values corresponding to the StreamReadFeatures and
# their boolean value, for example:
#
# stream-read-features {
# STRICT_DUPLICATE_DETECTION = on
# }
stream-read-features {}
# Configuration of the JsonFactory StreamWriteFeature.
# See com.fasterxml.jackson.core.StreamWriteFeature
# Enum values corresponding to the StreamWriteFeatures and
# their boolean value, for example:
#
# stream-write-features {
# WRITE_BIGDECIMAL_AS_PLAIN = on
# }
stream-write-features {}
# Configuration of the JsonFactory JsonReadFeature.
# See com.fasterxml.jackson.core.json.JsonReadFeature
# Enum values corresponding to the JsonReadFeatures and
# their boolean value, for example:
#
# json-read-features {
# ALLOW_SINGLE_QUOTES = on
# }
json-read-features {}
# Configuration of the JsonFactory JsonWriteFeature.
# See com.fasterxml.jackson.core.json.JsonWriteFeature
# Enum values corresponding to the JsonWriteFeatures and
# their boolean value, for example:
#
# json-write-features {
# WRITE_NUMBERS_AS_STRINGS = on
# }
json-write-features {}
# Additional classes that are allowed even if they are not defined in `serialization-bindings`.
# This is useful when a class is not used for serialization any more and therefore removed
# from `serialization-bindings`, but should still be possible to deserialize.

View file

@ -11,7 +11,6 @@ import scala.collection.immutable
import scala.compat.java8.OptionConverters._
import scala.util.Failure
import scala.util.Success
import akka.actor.ActorSystem
import akka.actor.DynamicAccess
import akka.actor.ExtendedActorSystem
@ -27,6 +26,7 @@ import com.fasterxml.jackson.annotation.JsonAutoDetect
import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.PropertyAccessor
import com.fasterxml.jackson.core.JsonFactory
import com.fasterxml.jackson.core.JsonFactoryBuilder
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.MapperFeature
import com.fasterxml.jackson.databind.Module
@ -36,6 +36,11 @@ import com.fasterxml.jackson.module.paramnames.ParameterNamesModule
import com.typesafe.config.Config
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.StreamReadFeature
import com.fasterxml.jackson.core.StreamWriteFeature
import com.fasterxml.jackson.core.json.JsonReadFeature
import com.fasterxml.jackson.core.json.JsonWriteFeature
import com.fasterxml.jackson.databind.json.JsonMapper
object JacksonObjectMapperProvider extends ExtensionId[JacksonObjectMapperProvider] with ExtensionIdProvider {
override def get(system: ActorSystem): JacksonObjectMapperProvider = super.get(system)
@ -57,25 +62,65 @@ object JacksonObjectMapperProvider extends ExtensionId[JacksonObjectMapperProvid
baseConf
}
/**
* INTERNAL API: Use [[JacksonObjectMapperProvider#create]]
*
* This is needed by one test in Lagom where the ObjectMapper is created without starting and ActorSystem.
*/
@InternalStableApi
def createObjectMapper(
private def createJsonFactory(
bindingName: String,
jsonFactory: Option[JsonFactory],
objectMapperFactory: JacksonObjectMapperFactory,
config: Config,
dynamicAccess: DynamicAccess,
log: Option[LoggingAdapter]) = {
baseJsonFactory: Option[JsonFactory]): JsonFactory = {
import akka.util.ccompat.JavaConverters._
val jsonFactoryBuilder = baseJsonFactory match {
case Some(jsonFactory) => new JsonFactoryBuilder(jsonFactory)
case None => new JsonFactoryBuilder()
}
val mapper = objectMapperFactory.newObjectMapper(bindingName, jsonFactory)
val configuredStreamReadFeatures =
features(config, "stream-read-features").map {
case (enumName, value) => StreamReadFeature.valueOf(enumName) -> value
}
val streamReadFeatures =
objectMapperFactory.overrideConfiguredStreamReadFeatures(bindingName, configuredStreamReadFeatures)
streamReadFeatures.foreach {
case (feature, value) => jsonFactoryBuilder.configure(feature, value)
}
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
val configuredStreamWriteFeatures =
features(config, "stream-write-features").map {
case (enumName, value) => StreamWriteFeature.valueOf(enumName) -> value
}
val streamWriteFeatures =
objectMapperFactory.overrideConfiguredStreamWriteFeatures(bindingName, configuredStreamWriteFeatures)
streamWriteFeatures.foreach {
case (feature, value) => jsonFactoryBuilder.configure(feature, value)
}
val configuredJsonReadFeatures =
features(config, "json-read-features").map {
case (enumName, value) => JsonReadFeature.valueOf(enumName) -> value
}
val jsonReadFeatures =
objectMapperFactory.overrideConfiguredJsonReadFeatures(bindingName, configuredJsonReadFeatures)
jsonReadFeatures.foreach {
case (feature, value) => jsonFactoryBuilder.configure(feature, value)
}
val configuredJsonWriteFeatures =
features(config, "json-write-features").map {
case (enumName, value) => JsonWriteFeature.valueOf(enumName) -> value
}
val jsonWriteFeatures =
objectMapperFactory.overrideConfiguredJsonWriteFeatures(bindingName, configuredJsonWriteFeatures)
jsonWriteFeatures.foreach {
case (feature, value) => jsonFactoryBuilder.configure(feature, value)
}
jsonFactoryBuilder.build()
}
private def configureObjectMapperFeatures(
bindingName: String,
objectMapper: ObjectMapper,
objectMapperFactory: JacksonObjectMapperFactory,
config: Config): Unit = {
val configuredSerializationFeatures =
features(config, "serialization-features").map {
@ -84,7 +129,7 @@ object JacksonObjectMapperProvider extends ExtensionId[JacksonObjectMapperProvid
val serializationFeatures =
objectMapperFactory.overrideConfiguredSerializationFeatures(bindingName, configuredSerializationFeatures)
serializationFeatures.foreach {
case (feature, value) => mapper.configure(feature, value)
case (feature, value) => objectMapper.configure(feature, value)
}
val configuredDeserializationFeatures =
@ -94,7 +139,7 @@ object JacksonObjectMapperProvider extends ExtensionId[JacksonObjectMapperProvid
val deserializationFeatures =
objectMapperFactory.overrideConfiguredDeserializationFeatures(bindingName, configuredDeserializationFeatures)
deserializationFeatures.foreach {
case (feature, value) => mapper.configure(feature, value)
case (feature, value) => objectMapper.configure(feature, value)
}
val configuredMapperFeatures = features(config, "mapper-features").map {
@ -102,7 +147,7 @@ object JacksonObjectMapperProvider extends ExtensionId[JacksonObjectMapperProvid
}
val mapperFeatures = objectMapperFactory.overrideConfiguredMapperFeatures(bindingName, configuredMapperFeatures)
mapperFeatures.foreach {
case (feature, value) => mapper.configure(feature, value)
case (feature, value) => objectMapper.configure(feature, value)
}
val configuredJsonParserFeatures = features(config, "json-parser-features").map {
@ -111,7 +156,7 @@ object JacksonObjectMapperProvider extends ExtensionId[JacksonObjectMapperProvid
val jsonParserFeatures =
objectMapperFactory.overrideConfiguredJsonParserFeatures(bindingName, configuredJsonParserFeatures)
jsonParserFeatures.foreach {
case (feature, value) => mapper.configure(feature, value)
case (feature, value) => objectMapper.configure(feature, value)
}
val configuredJsonGeneratorFeatures = features(config, "json-generator-features").map {
@ -120,8 +165,19 @@ object JacksonObjectMapperProvider extends ExtensionId[JacksonObjectMapperProvid
val jsonGeneratorFeatures =
objectMapperFactory.overrideConfiguredJsonGeneratorFeatures(bindingName, configuredJsonGeneratorFeatures)
jsonGeneratorFeatures.foreach {
case (feature, value) => mapper.configure(feature, value)
case (feature, value) => objectMapper.configure(feature, value)
}
}
private def configureObjectMapperModules(
bindingName: String,
objectMapper: ObjectMapper,
objectMapperFactory: JacksonObjectMapperFactory,
config: Config,
dynamicAccess: DynamicAccess,
log: Option[LoggingAdapter]): Unit = {
import akka.util.ccompat.JavaConverters._
val configuredModules = config.getStringList("jackson-modules").asScala
val modules1 =
@ -154,11 +210,32 @@ object JacksonObjectMapperProvider extends ExtensionId[JacksonObjectMapperProvid
val modules3 = objectMapperFactory.overrideConfiguredModules(bindingName, modules2)
modules3.foreach { module =>
mapper.registerModule(module)
objectMapper.registerModule(module)
log.foreach(_.debug("Registered Jackson module [{}]", module.getClass.getName))
}
}
mapper
/**
* INTERNAL API: Use [[JacksonObjectMapperProvider#create]]
*
* This is needed by one test in Lagom where the ObjectMapper is created without starting and ActorSystem.
*/
@InternalStableApi
def createObjectMapper(
bindingName: String,
jsonFactory: Option[JsonFactory],
objectMapperFactory: JacksonObjectMapperFactory,
config: Config,
dynamicAccess: DynamicAccess,
log: Option[LoggingAdapter]): ObjectMapper = {
val configuredJsonFactory = createJsonFactory(bindingName, objectMapperFactory, config, jsonFactory)
val mapper = objectMapperFactory.newObjectMapper(bindingName, configuredJsonFactory)
configureObjectMapperFeatures(bindingName, mapper, objectMapperFactory, config)
configureObjectMapperModules(bindingName, mapper, objectMapperFactory, config, dynamicAccess, log)
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
}
private def isModuleEnabled(fqcn: String, dynamicAccess: DynamicAccess): Boolean =
@ -300,8 +377,25 @@ class JacksonObjectMapperFactory {
* @param jsonFactory optional `JsonFactory` such as `CBORFactory`, for plain JSON `None` (defaults)
* can be used
*/
def newObjectMapper(@unused bindingName: String, jsonFactory: Option[JsonFactory]): ObjectMapper =
new ObjectMapper(jsonFactory.orNull)
def newObjectMapper(@unused bindingName: String, jsonFactory: JsonFactory): ObjectMapper =
JsonMapper.builder(jsonFactory).build()
/**
* After construction of the `ObjectMapper` the configured modules are added to
* the mapper. These modules can be amended programatically by overriding this method and
* return the modules that are to be applied to the `ObjectMapper`.
*
* When implementing a `JacksonObjectMapperFactory` with Java the `immutable.Seq` can be
* created with [[akka.japi.Util.immutableSeq]].
*
* @param bindingName bindingName name of this `ObjectMapper`
* @param configuredModules the list of `Modules` that were configured in
* `akka.serialization.jackson.deserialization-features`
*/
def overrideConfiguredModules(
@unused bindingName: String,
configuredModules: immutable.Seq[Module]): immutable.Seq[Module] =
configuredModules
/**
* After construction of the `ObjectMapper` the configured serialization features are applied to
@ -339,23 +433,6 @@ class JacksonObjectMapperFactory {
: immutable.Seq[(DeserializationFeature, Boolean)] =
configuredFeatures
/**
* After construction of the `ObjectMapper` the configured modules are added to
* the mapper. These modules can be amended programatically by overriding this method and
* return the modules that are to be applied to the `ObjectMapper`.
*
* When implementing a `JacksonObjectMapperFactory` with Java the `immutable.Seq` can be
* created with [[akka.japi.Util.immutableSeq]].
*
* @param bindingName bindingName name of this `ObjectMapper`
* @param configuredModules the list of `Modules` that were configured in
* `akka.serialization.jackson.deserialization-features`
*/
def overrideConfiguredModules(
@unused bindingName: String,
configuredModules: immutable.Seq[Module]): immutable.Seq[Module] =
configuredModules
/**
* After construction of the `ObjectMapper` the configured mapper features are applied to
* the mapper. These features can be amended programmatically by overriding this method and
@ -395,4 +472,56 @@ class JacksonObjectMapperFactory {
configuredFeatures: immutable.Seq[(JsonGenerator.Feature, Boolean)])
: immutable.Seq[(JsonGenerator.Feature, Boolean)] =
configuredFeatures
/**
* `StreamReadFeature`s used to configure the `JsonFactoryBuilder` that, if provided, will later be used to create
* an `ObjectMapper`. These features can be amended programmatically by overriding this method and return the features
* that are to be applied to the `JsonFactoryBuilder`.
*
* @param bindingName bindingName name of this `ObjectMapper`
* @param configuredFeatures the list of `StreamReadFeature` that were configured in `akka.serialization.jackson.stream-read-features`
*/
def overrideConfiguredStreamReadFeatures(
@unused bindingName: String,
configuredFeatures: immutable.Seq[(StreamReadFeature, Boolean)]): immutable.Seq[(StreamReadFeature, Boolean)] =
configuredFeatures
/**
* `StreamWriteFeature`s used to configure the `JsonFactoryBuilder` that, if provided, will later be used to create
* an `ObjectMapper`. These features can be amended programmatically by overriding this method and return the features
* that are to be applied to the `JsonFactoryBuilder`.
*
* @param bindingName bindingName name of this `ObjectMapper`
* @param configuredFeatures the list of `StreamWriterFeature` that were configured in `akka.serialization.jackson.stream-write-features`
*/
def overrideConfiguredStreamWriteFeatures(
@unused bindingName: String,
configuredFeatures: immutable.Seq[(StreamWriteFeature, Boolean)]): immutable.Seq[(StreamWriteFeature, Boolean)] =
configuredFeatures
/**
* `JsonReadFeature`s used to configure the `JsonFactoryBuilder` that, if provided, will later be used to create
* an `ObjectMapper`. These features can be amended programmatically by overriding this method and return the features
* that are to be applied to the `JsonFactoryBuilder`.
*
* @param bindingName bindingName name of this `ObjectMapper`
* @param configuredFeatures the list of `JsonReadFeature` that were configured in `akka.serialization.jackson.json-read-features`
*/
def overrideConfiguredJsonReadFeatures(
@unused bindingName: String,
configuredFeatures: immutable.Seq[(JsonReadFeature, Boolean)]): immutable.Seq[(JsonReadFeature, Boolean)] =
configuredFeatures
/**
* `JsonWriteFeature`s used to configure the `JsonFactoryBuilder` that, if provided, will later be used to create
* an `ObjectMapper`. These features can be amended programmatically by overriding this method and return the features
* that are to be applied to the `JsonFactoryBuilder`.
*
* @param bindingName bindingName name of this `ObjectMapper`
* @param configuredFeatures the list of `JsonWriteFeature` that were configured in `akka.serialization.jackson.json-write-features`
*/
def overrideConfiguredJsonWriteFeatures(
@unused bindingName: String,
configuredFeatures: immutable.Seq[(JsonWriteFeature, Boolean)]): immutable.Seq[(JsonWriteFeature, Boolean)] =
configuredFeatures
}

View file

@ -16,7 +16,6 @@ import java.util.logging.FileHandler
import scala.collection.immutable
import scala.concurrent.duration.FiniteDuration
import scala.concurrent.duration._
import akka.actor.ActorRef
import akka.actor.ActorSystem
import akka.actor.Address
@ -48,6 +47,10 @@ import org.scalatest.Matchers
import org.scalatest.WordSpecLike
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.StreamReadFeature
import com.fasterxml.jackson.core.StreamWriteFeature
import com.fasterxml.jackson.databind.json.JsonMapper
import com.github.ghik.silencer.silent
object ScalaTestMessages {
trait TestMessage
@ -109,6 +112,7 @@ class ScalaTestEventMigration extends JacksonMigration {
class JacksonCborSerializerSpec extends JacksonSerializerSpec("jackson-cbor")
@silent // this test uses Jackson deprecated APIs
class JacksonJsonSerializerSpec extends JacksonSerializerSpec("jackson-json") {
def serializeToJsonString(obj: AnyRef, sys: ActorSystem = system): String = {
@ -161,6 +165,18 @@ class JacksonJsonSerializerSpec extends JacksonSerializerSpec("jackson-json") {
# on is Jackson's default
json-generator-features.AUTO_CLOSE_TARGET = off
# off is Jackson's default
stream-read-features.STRICT_DUPLICATE_DETECTION = on
# off is Jackson's default
stream-write-features.WRITE_BIGDECIMAL_AS_PLAIN = on
# off is Jackson's default
json-read-features.ALLOW_YAML_COMMENTS = on
# off is Jackson's default
json-write-features.ESCAPE_NON_ASCII = on
}
""") { sys =>
val identifiedObjectMapper =
@ -174,7 +190,7 @@ class JacksonJsonSerializerSpec extends JacksonSerializerSpec("jackson-json") {
namedObjectMapper.isEnabled(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS) should ===(false)
// Default mapper follows Jackson and reference.conf default configuration
defaultObjectMapper.isEnabled(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS) should ===(true)
defaultObjectMapper.isEnabled(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS) should ===(false)
}
"support deserialization features" in {
@ -209,14 +225,59 @@ class JacksonJsonSerializerSpec extends JacksonSerializerSpec("jackson-json") {
defaultObjectMapper.isEnabled(JsonGenerator.Feature.AUTO_CLOSE_TARGET) should ===(true)
}
"support stream read features" in {
identifiedObjectMapper.isEnabled(StreamReadFeature.STRICT_DUPLICATE_DETECTION) should ===(true)
namedObjectMapper.isEnabled(StreamReadFeature.STRICT_DUPLICATE_DETECTION) should ===(true)
// Default mapper follows Jackson and reference.conf default configuration
defaultObjectMapper.isEnabled(StreamReadFeature.STRICT_DUPLICATE_DETECTION) should ===(false)
}
"support stream write features" in {
identifiedObjectMapper.isEnabled(StreamWriteFeature.WRITE_BIGDECIMAL_AS_PLAIN) should ===(true)
namedObjectMapper.isEnabled(StreamWriteFeature.WRITE_BIGDECIMAL_AS_PLAIN) should ===(true)
// Default mapper follows Jackson and reference.conf default configuration
defaultObjectMapper.isEnabled(StreamWriteFeature.WRITE_BIGDECIMAL_AS_PLAIN) should ===(false)
}
"support json read features" in {
// ATTENTION: this is trick. Although we are configuring `json-read-features`, Jackson
// does not provides a way to check for `StreamReadFeature`s, so we need to check for
// `JsonParser.Feature`.ALLOW_YAML_COMMENTS.
// Same applies for json-write-features and JsonGenerator.Feature.
identifiedObjectMapper.isEnabled(JsonParser.Feature.ALLOW_YAML_COMMENTS) should ===(true)
namedObjectMapper.isEnabled(JsonParser.Feature.ALLOW_YAML_COMMENTS) should ===(true)
// Default mapper follows Jackson and reference.conf default configuration
defaultObjectMapper.isEnabled(JsonParser.Feature.ALLOW_YAML_COMMENTS) should ===(false)
}
"support json write features" in {
// ATTENTION: this is trickier than `json-read-features` vs JsonParser.Feature
// since the JsonWriteFeature replaces deprecated APIs in JsonGenerator.Feature.
// But just like the test for `json-read-features` there is no API to check for
// `JsonWriteFeature`s, so we need to use the deprecated APIs.
identifiedObjectMapper.isEnabled(JsonGenerator.Feature.ESCAPE_NON_ASCII) should ===(true)
namedObjectMapper.isEnabled(JsonGenerator.Feature.ESCAPE_NON_ASCII) should ===(true)
// Default mapper follows Jackson and reference.conf default configuration
defaultObjectMapper.isEnabled(JsonGenerator.Feature.ESCAPE_NON_ASCII) should ===(false)
}
"fallback to defaults when object mapper is not configured" in {
val notConfigured = JacksonObjectMapperProvider(sys).getOrCreate("jackson-not-configured", None)
// Use Jacksons and Akka defaults
notConfigured.isEnabled(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS) should ===(true)
notConfigured.isEnabled(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS) should ===(false)
notConfigured.isEnabled(DeserializationFeature.EAGER_DESERIALIZER_FETCH) should ===(true)
notConfigured.isEnabled(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) should ===(false)
notConfigured.isEnabled(JsonParser.Feature.ALLOW_COMMENTS) should ===(false)
notConfigured.isEnabled(JsonGenerator.Feature.AUTO_CLOSE_TARGET) should ===(true)
notConfigured.isEnabled(StreamReadFeature.STRICT_DUPLICATE_DETECTION) should ===(false)
notConfigured.isEnabled(StreamWriteFeature.WRITE_BIGDECIMAL_AS_PLAIN) should ===(false)
notConfigured.isEnabled(JsonParser.Feature.ALLOW_YAML_COMMENTS) should ===(false)
notConfigured.isEnabled(JsonGenerator.Feature.ESCAPE_NON_ASCII) should ===(false)
}
}
}
@ -253,6 +314,7 @@ class JacksonJsonSerializerSpec extends JacksonSerializerSpec("jackson-json") {
withSystem("""
akka.serialization.jackson.serialization-features {
WRITE_DATES_AS_TIMESTAMPS = on
WRITE_DURATIONS_AS_TIMESTAMPS = on
}
""") { sys =>
val msg = new TimeCommand(LocalDateTime.of(2019, 4, 29, 23, 15, 3, 12345), Duration.of(5, ChronoUnit.SECONDS))
@ -304,9 +366,9 @@ class JacksonJsonSerializerSpec extends JacksonSerializerSpec("jackson-json") {
"be possible to create custom ObjectMapper" in {
val customJacksonObjectMapperFactory = new JacksonObjectMapperFactory {
override def newObjectMapper(bindingName: String, jsonFactory: Option[JsonFactory]): ObjectMapper = {
override def newObjectMapper(bindingName: String, jsonFactory: JsonFactory): ObjectMapper = {
if (bindingName == "jackson-json") {
val mapper = new ObjectMapper(jsonFactory.orNull)
val mapper: ObjectMapper = JsonMapper.builder(jsonFactory).build()
// some customer configuration of the mapper
mapper.setLocale(Locale.US)
mapper
@ -354,7 +416,7 @@ class JacksonJsonSerializerSpec extends JacksonSerializerSpec("jackson-json") {
configuredFeatures: immutable.Seq[(JsonGenerator.Feature, Boolean)])
: immutable.Seq[(JsonGenerator.Feature, Boolean)] =
if (bindingName == "jackson-json")
configuredFeatures :+ (JsonGenerator.Feature.WRITE_NUMBERS_AS_STRINGS -> true)
configuredFeatures :+ (JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN -> true)
else
super.overrideConfiguredJsonGeneratorFeatures(bindingName, configuredFeatures)
}
@ -370,14 +432,14 @@ class JacksonJsonSerializerSpec extends JacksonSerializerSpec("jackson-json") {
mapper.isEnabled(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) should ===(true)
mapper.isEnabled(JsonParser.Feature.ALLOW_SINGLE_QUOTES) should ===(true)
mapper.isEnabled(SerializationFeature.INDENT_OUTPUT) should ===(true)
mapper.isEnabled(JsonGenerator.Feature.WRITE_NUMBERS_AS_STRINGS) should ===(true)
mapper.isEnabled(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN) should ===(true)
val msg = InstantCommand(Instant.ofEpochMilli(1559907792075L))
val json = serializeToJsonString(msg, sys)
// using the custom ObjectMapper with pretty printing enabled, and no JavaTimeModule
json should include(""" "instant" : {""")
json should include(""" "nanos" : "75000000",""")
json should include(""" "seconds" : "1559907792"""")
json should include(""" "nanos" : 75000000,""")
json should include(""" "seconds" : 1559907792""")
}
}

View file

@ -120,13 +120,14 @@ object SerializationDocSpec {
#//#date-time
akka.serialization.jackson.serialization-features {
WRITE_DATES_AS_TIMESTAMPS = on
WRITE_DURATIONS_AS_TIMESTAMPS = on
}
#//#date-time
"""
val configWhitelist = """
#//#whitelist-class-prefix
akka.serialization.jackson.whitelist-class-prefix =
akka.serialization.jackson.whitelist-class-prefix =
["com.myservice.event.OrderAdded", "com.myservice.command"]
#//#whitelist-class-prefix
"""
@ -144,7 +145,7 @@ class SerializationDocSpec
"jdoc.akka.serialization.jackson.v2c.ItemAdded" = "jdoc.akka.serialization.jackson.v2c.ItemAddedMigration"
"jdoc.akka.serialization.jackson.v2a.Customer" = "jdoc.akka.serialization.jackson.v2a.CustomerMigration"
"jdoc.akka.serialization.jackson.v1.OrderAdded" = "jdoc.akka.serialization.jackson.v2a.OrderPlacedMigration"
# migrations for Scala classes
"doc.akka.serialization.jackson.v2b.ItemAdded" = "doc.akka.serialization.jackson.v2b.ItemAddedMigration"
"doc.akka.serialization.jackson.v2c.ItemAdded" = "doc.akka.serialization.jackson.v2c.ItemAddedMigration"

View file

@ -21,8 +21,8 @@ object Dependencies {
// needs to be inline with the aeron version
val agronaVersion = "1.0.7"
val nettyVersion = "3.10.6.Final"
val jacksonVersion = "2.9.10"
val jacksonDatabindVersion = "2.9.10"
val jacksonVersion = "2.10.0"
val jacksonDatabindVersion = "2.10.0"
val protobufJavaVersion = "3.9.2"
val logbackVersion = "1.2.3"