diff --git a/akka-serialization-jackson/src/main/resources/reference.conf b/akka-serialization-jackson/src/main/resources/reference.conf index fd041f9012..4924f6ed4b 100644 --- a/akka-serialization-jackson/src/main/resources/reference.conf +++ b/akka-serialization-jackson/src/main/resources/reference.conf @@ -126,6 +126,20 @@ akka.serialization.jackson { # } json-write-features {} + # Configuration of the JsonFactory Visibility. + # See com.fasterxml.jackson.annotation.PropertyAccessor + # and com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility + # Enum values. For example, to serialize only public fields + # overwrite the default values with: + # + # visibility { + # FIELD = PUBLIC_ONLY + # } + # Default: all fields (including private and protected) are serialized. + visibility { + FIELD = ANY + } + # Deprecated, use `allowed-class-prefix` instead whitelist-class-prefix = [] diff --git a/akka-serialization-jackson/src/main/scala/akka/serialization/jackson/JacksonObjectMapperProvider.scala b/akka-serialization-jackson/src/main/scala/akka/serialization/jackson/JacksonObjectMapperProvider.scala index c011db3367..b755bf31de 100644 --- a/akka-serialization-jackson/src/main/scala/akka/serialization/jackson/JacksonObjectMapperProvider.scala +++ b/akka-serialization-jackson/src/main/scala/akka/serialization/jackson/JacksonObjectMapperProvider.scala @@ -179,6 +179,25 @@ object JacksonObjectMapperProvider extends ExtensionId[JacksonObjectMapperProvid } } + private def configureObjectVisibility( + bindingName: String, + objectMapper: ObjectMapper, + objectMapperFactory: JacksonObjectMapperFactory, + config: Config): Unit = { + + val configuredVisibility: immutable.Seq[(PropertyAccessor, JsonAutoDetect.Visibility)] = + configPairs(config, "visibility").map { + case (property, visibility) => + PropertyAccessor.valueOf(property) -> JsonAutoDetect.Visibility.valueOf(visibility) + } + val visibility = + objectMapperFactory.overrideConfiguredVisibility(bindingName, configuredVisibility) + visibility.foreach { + case (property, visibility) => objectMapper.setVisibility(property, visibility) + } + + } + private def configureObjectMapperModules( bindingName: String, objectMapper: ObjectMapper, @@ -244,8 +263,9 @@ object JacksonObjectMapperProvider extends ExtensionId[JacksonObjectMapperProvid configureObjectMapperFeatures(bindingName, mapper, objectMapperFactory, config) configureObjectMapperModules(bindingName, mapper, objectMapperFactory, config, dynamicAccess, log) + configureObjectVisibility(bindingName, mapper, objectMapperFactory, config) - mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) + mapper } private def isModuleEnabled(fqcn: String, dynamicAccess: DynamicAccess): Boolean = @@ -264,6 +284,12 @@ object JacksonObjectMapperProvider extends ExtensionId[JacksonObjectMapperProvid val cfg = config.getConfig(section) cfg.root.keySet().asScala.map(key => key -> cfg.getBoolean(key)).toList } + + private def configPairs(config: Config, section: String): immutable.Seq[(String, String)] = { + import akka.util.ccompat.JavaConverters._ + val cfg = config.getConfig(section) + cfg.root.keySet().asScala.map(key => key -> cfg.getString(key)).toList + } } /** @@ -534,4 +560,20 @@ class JacksonObjectMapperFactory { @unused bindingName: String, configuredFeatures: immutable.Seq[(JsonWriteFeature, Boolean)]): immutable.Seq[(JsonWriteFeature, Boolean)] = configuredFeatures + + /** + * Visibility settings used to configure the `JsonFactoryBuilder` that, if provided, will later be used to create + * an `ObjectMapper`. These settings can be amended programmatically by overriding this method and return the values + * that are to be applied to the `JsonFactoryBuilder`. + * + * @param bindingName bindingName name of this `ObjectMapper` + * @param configuredFeatures the list of `PropertyAccessor`/`JsonAutoDetect.Visibility` that were configured in + * `akka.serialization.jackson.visibility` + */ + def overrideConfiguredVisibility( + @unused bindingName: String, + configuredFeatures: immutable.Seq[(PropertyAccessor, JsonAutoDetect.Visibility)]) + : immutable.Seq[(PropertyAccessor, JsonAutoDetect.Visibility)] = + configuredFeatures + } diff --git a/akka-serialization-jackson/src/test/java/akka/serialization/jackson/JavaTestMessages.java b/akka-serialization-jackson/src/test/java/akka/serialization/jackson/JavaTestMessages.java index 3bc178a6cc..cd5fbffaa8 100644 --- a/akka-serialization-jackson/src/test/java/akka/serialization/jackson/JavaTestMessages.java +++ b/akka-serialization-jackson/src/test/java/akka/serialization/jackson/JavaTestMessages.java @@ -528,4 +528,15 @@ public interface JavaTestMessages { return name != null ? name.hashCode() : 0; } } + + // A class with non-public fields + final class ClassWithVisibility { + public final String publicField = "1234"; + final String defaultField = "abcd"; + protected final String protectedField = "vwxyz"; + private final String privateField = "ABCD"; + + @JsonCreator + public ClassWithVisibility() {} + } } diff --git a/akka-serialization-jackson/src/test/scala/akka/serialization/jackson/JacksonSerializerSpec.scala b/akka-serialization-jackson/src/test/scala/akka/serialization/jackson/JacksonSerializerSpec.scala index 38780bac8a..2b140d3cbc 100644 --- a/akka-serialization-jackson/src/test/scala/akka/serialization/jackson/JacksonSerializerSpec.scala +++ b/akka-serialization-jackson/src/test/scala/akka/serialization/jackson/JacksonSerializerSpec.scala @@ -411,6 +411,46 @@ class JacksonJsonSerializerSpec extends JacksonSerializerSpec("jackson-json") { "be possible to create custom ObjectMapper" in { pending } + + "be possible to tune the visibility at ObjectMapper level (FIELD, PUBLIC_ONLY)" in { + withSystem(""" + akka.actor { + serialization-bindings { + "akka.serialization.jackson.JavaTestMessages$ClassWithVisibility" = jackson-json + } + } + akka.serialization.jackson.visibility { + FIELD = PUBLIC_ONLY + } + """) { sys => + val msg = new ClassWithVisibility(); + val json = serializeToJsonString(msg, sys) + val expected = """{"publicField":"1234"}""" + json should ===(expected) + } + } + + // This test ensures the default behavior in Akka 2.6 series + // (that is "FIELD = ANY") stays consistent + "be possible to tune the visibility at ObjectMapper level (Akka default)" in { + withSystem(""" + akka.actor { + serialization-bindings { + "akka.serialization.jackson.JavaTestMessages$ClassWithVisibility" = jackson-json + } + } + akka.serialization.jackson.visibility { + ## No overrides + } + """) { sys => + val msg = new ClassWithVisibility(); + val json = serializeToJsonString(msg, sys) + val expected = + """{"publicField":"1234","defaultField":"abcd","protectedField":"vwxyz","privateField":"ABCD"}""".stripMargin + json should ===(expected) + } + } + } "JacksonJsonSerializer with Scala message classes" must {