Add configuration for all Jackson Features (#27409)

* Support configuration for Jackson MapperFeatures in Jackson Serializer

* Add JsonParser.Feature configuration support

* Add JsonGenerator.Feature configuration support

* Fix formatting issues

* Add examples for each feature configuration

* Test coverage of the override methods
This commit is contained in:
Marcos Pereira 2019-08-16 05:33:47 -04:00 committed by Arnout Engelen
parent 16f4971f64
commit 9caae087a2
3 changed files with 212 additions and 17 deletions

View file

@ -32,6 +32,7 @@ import akka.testkit.TestKit
import com.fasterxml.jackson.annotation.JsonSubTypes
import com.fasterxml.jackson.annotation.JsonTypeInfo
import com.fasterxml.jackson.core.JsonFactory
import com.fasterxml.jackson.databind.MapperFeature
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.Module
@ -45,6 +46,8 @@ import com.typesafe.config.ConfigFactory
import org.scalatest.BeforeAndAfterAll
import org.scalatest.Matchers
import org.scalatest.WordSpecLike
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.JsonGenerator
object ScalaTestMessages {
trait TestMessage
@ -137,23 +140,84 @@ class JacksonJsonSerializerSpec extends JacksonSerializerSpec("jackson-json") {
JacksonObjectMapperProvider(system).getOrCreate(anotherBindingName, None) shouldBe theSameInstanceAs(mapper2)
}
"support several different configurations" in {
"JacksonSerializer configuration" must {
withSystem("""
akka.actor.serializers.jackson-json2 = "akka.serialization.jackson.JacksonJsonSerializer"
akka.actor.serialization-identifiers.jackson-json2 = 999
akka.serialization.jackson.jackson-json2 {
deserialization-features.FAIL_ON_UNKNOWN_PROPERTIES = on
}
""") { sys =>
val objMapper2 = serialization(sys).serializerByIdentity(999).asInstanceOf[JacksonJsonSerializer].objectMapper
objMapper2.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) should ===(true)
val objMapper3 = JacksonObjectMapperProvider(sys).getOrCreate("jackson-json2", None)
objMapper3.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) should ===(true)
// default has different config, different instance but same JacksonJsonSerializer class
val objMapper =
# on is Jackson's default
serialization-features.WRITE_DURATIONS_AS_TIMESTAMPS = off
# on is Jackson's default
deserialization-features.EAGER_DESERIALIZER_FETCH = off
# off is Jackson's default
mapper-features.SORT_PROPERTIES_ALPHABETICALLY = on
# off is Jackson's default
json-parser-features.ALLOW_COMMENTS = on
# on is Jackson's default
json-generator-features.AUTO_CLOSE_TARGET = off
}
""") { sys =>
val identifiedObjectMapper =
serialization(sys).serializerByIdentity(999).asInstanceOf[JacksonJsonSerializer].objectMapper
val namedObjectMapper = JacksonObjectMapperProvider(sys).getOrCreate("jackson-json2", None)
val defaultObjectMapper =
serializerFor(ScalaTestMessages.SimpleCommand("abc")).asInstanceOf[JacksonJsonSerializer].objectMapper
objMapper.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) should ===(false)
"support serialization features" in {
identifiedObjectMapper.isEnabled(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS) should ===(false)
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)
}
"support deserialization features" in {
identifiedObjectMapper.isEnabled(DeserializationFeature.EAGER_DESERIALIZER_FETCH) should ===(false)
namedObjectMapper.isEnabled(DeserializationFeature.EAGER_DESERIALIZER_FETCH) should ===(false)
// Default mapper follows Jackson and reference.conf default configuration
defaultObjectMapper.isEnabled(DeserializationFeature.EAGER_DESERIALIZER_FETCH) should ===(true)
}
"support mapper features" in {
identifiedObjectMapper.isEnabled(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) should ===(true)
namedObjectMapper.isEnabled(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) should ===(true)
// Default mapper follows Jackson and reference.conf default configuration
defaultObjectMapper.isEnabled(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) should ===(false)
}
"support json parser features" in {
identifiedObjectMapper.isEnabled(JsonParser.Feature.ALLOW_COMMENTS) should ===(true)
namedObjectMapper.isEnabled(JsonParser.Feature.ALLOW_COMMENTS) should ===(true)
// Default mapper follows Jackson and reference.conf default configuration
defaultObjectMapper.isEnabled(JsonParser.Feature.ALLOW_COMMENTS) should ===(false)
}
"support json generator features" in {
identifiedObjectMapper.isEnabled(JsonGenerator.Feature.AUTO_CLOSE_TARGET) should ===(false)
namedObjectMapper.isEnabled(JsonGenerator.Feature.AUTO_CLOSE_TARGET) should ===(false)
// Default mapper follows Jackson and reference.conf default configuration
defaultObjectMapper.isEnabled(JsonGenerator.Feature.AUTO_CLOSE_TARGET) should ===(true)
}
"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(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)
}
}
}
}
@ -254,19 +318,45 @@ class JacksonJsonSerializerSpec extends JacksonSerializerSpec("jackson-json") {
bindingName: String,
configuredFeatures: immutable.Seq[(SerializationFeature, Boolean)])
: immutable.Seq[(SerializationFeature, Boolean)] = {
if (bindingName == "jackson-json") {
if (bindingName == "jackson-json")
configuredFeatures :+ (SerializationFeature.INDENT_OUTPUT -> true)
} else
else
super.overrideConfiguredSerializationFeatures(bindingName, configuredFeatures)
}
override def overrideConfiguredModules(
bindingName: String,
configuredModules: immutable.Seq[Module]): immutable.Seq[Module] =
if (bindingName == "jackson-json") {
if (bindingName == "jackson-json")
configuredModules.filterNot(_.isInstanceOf[JavaTimeModule])
} else
else
super.overrideConfiguredModules(bindingName, configuredModules)
override def overrideConfiguredMapperFeatures(
bindingName: String,
configuredFeatures: immutable.Seq[(MapperFeature, Boolean)]): immutable.Seq[(MapperFeature, Boolean)] =
if (bindingName == "jackson-json")
configuredFeatures :+ (MapperFeature.SORT_PROPERTIES_ALPHABETICALLY -> true)
else
super.overrideConfiguredMapperFeatures(bindingName, configuredFeatures)
override def overrideConfiguredJsonParserFeatures(
bindingName: String,
configuredFeatures: immutable.Seq[(JsonParser.Feature, Boolean)])
: immutable.Seq[(JsonParser.Feature, Boolean)] =
if (bindingName == "jackson-json")
configuredFeatures :+ (JsonParser.Feature.ALLOW_SINGLE_QUOTES -> true)
else
super.overrideConfiguredJsonParserFeatures(bindingName, configuredFeatures)
override def overrideConfiguredJsonGeneratorFeatures(
bindingName: String,
configuredFeatures: immutable.Seq[(JsonGenerator.Feature, Boolean)])
: immutable.Seq[(JsonGenerator.Feature, Boolean)] =
if (bindingName == "jackson-json")
configuredFeatures :+ (JsonGenerator.Feature.WRITE_NUMBERS_AS_STRINGS -> true)
else
super.overrideConfiguredJsonGeneratorFeatures(bindingName, configuredFeatures)
}
val config = system.settings.config
@ -275,12 +365,19 @@ class JacksonJsonSerializerSpec extends JacksonSerializerSpec("jackson-json") {
.withSetup(JacksonObjectMapperProviderSetup(customJacksonObjectMapperFactory))
.withSetup(BootstrapSetup(config))
withSystem(setup) { sys =>
val mapper = JacksonObjectMapperProvider(sys).getOrCreate("jackson-json", None)
mapper.isEnabled(SerializationFeature.INDENT_OUTPUT) should ===(true)
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)
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(""" "seconds" : 1559907792,""")
json should include(""" "nanos" : 75000000,""")
json should include(""" "nanos" : "75000000",""")
json should include(""" "seconds" : "1559907792"""")
}
}