diff --git a/akka-bench-jmh/src/main/scala/akka/serialization/jackson/JacksonSerializationBench.scala b/akka-bench-jmh/src/main/scala/akka/serialization/jackson/JacksonSerializationBench.scala index 83b0e632b3..beac8e3671 100644 --- a/akka-bench-jmh/src/main/scala/akka/serialization/jackson/JacksonSerializationBench.scala +++ b/akka-bench-jmh/src/main/scala/akka/serialization/jackson/JacksonSerializationBench.scala @@ -4,6 +4,8 @@ package akka.serialization.jackson +import java.time.Instant +import java.time.LocalDateTime import java.util.concurrent.TimeUnit import scala.concurrent.Await @@ -28,6 +30,11 @@ object JacksonSerializationBench { num1: Int, num2: Int, num3: Int, + flag1: Boolean, + flag2: Boolean, + duration: FiniteDuration, + date: LocalDateTime, + instant: Instant, nested1: Small, nested2: Small, nested3: Small) @@ -41,6 +48,9 @@ object JacksonSerializationBench { map: Map[String, Medium]) extends TestMessage + final class TimeMessage(val duration: FiniteDuration, val date: LocalDateTime, val instant: Instant) + extends TestMessage + // FIXME try with plain java classes (not case class) } @@ -56,9 +66,51 @@ class JacksonSerializationBench { val smallMsg1 = Small("abc", 17) val smallMsg2 = Small("def", 18) val smallMsg3 = Small("ghi", 19) - val mediumMsg1 = Medium("abc", "def", "ghi", 1, 2, 3, smallMsg1, smallMsg2, smallMsg3) - val mediumMsg2 = Medium("ABC", "DEF", "GHI", 10, 20, 30, smallMsg1, smallMsg2, smallMsg3) - val mediumMsg3 = Medium("abcABC", "defDEF", "ghiGHI", 100, 200, 300, smallMsg1, smallMsg2, smallMsg3) + val mediumMsg1 = Medium( + "abc", + "def", + "ghi", + 1, + 2, + 3, + false, + true, + 5.seconds, + LocalDateTime.of(2019, 4, 29, 23, 15, 3, 12345), + Instant.now(), + smallMsg1, + smallMsg2, + smallMsg3) + val mediumMsg2 = Medium( + "ABC", + "DEF", + "GHI", + 10, + 20, + 30, + true, + false, + 5.millis, + LocalDateTime.of(2019, 4, 29, 23, 15, 4, 12345), + Instant.now(), + smallMsg1, + smallMsg2, + smallMsg3) + val mediumMsg3 = Medium( + "abcABC", + "defDEF", + "ghiGHI", + 100, + 200, + 300, + true, + true, + 200.millis, + LocalDateTime.of(2019, 4, 29, 23, 15, 5, 12345), + Instant.now(), + smallMsg1, + smallMsg2, + smallMsg3) val largeMsg = Large( mediumMsg1, mediumMsg2, @@ -66,10 +118,12 @@ class JacksonSerializationBench { Vector(mediumMsg1, mediumMsg2, mediumMsg3), Map("a" -> mediumMsg1, "b" -> mediumMsg2, "c" -> mediumMsg3)) + val timeMsg = new TimeMessage(5.seconds, LocalDateTime.of(2019, 4, 29, 23, 15, 3, 12345), Instant.now()) + var system: ActorSystem = _ var serialization: Serialization = _ - @Param(Array("jackson-json", "jackson-smile", "jackson-cbor", "java")) + @Param(Array("jackson-json", "jackson-cbor", "jackson-smile")) // "java" private var serializerName: String = _ @Setup(Level.Trial) @@ -83,7 +137,11 @@ class JacksonSerializationBench { } } serialization.jackson { - #compress-larger-than = 100 b + compress-larger-than = 100000 b + + serialization-features { + #WRITE_DATES_AS_TIMESTAMPS = off + } } } """) @@ -127,4 +185,9 @@ class JacksonSerializationBench { serializeDeserialize(largeMsg) } + @Benchmark + def timeMessage(): TimeMessage = { + serializeDeserialize(timeMsg) + } + } diff --git a/akka-docs/src/main/paradox/serialization-jackson.md b/akka-docs/src/main/paradox/serialization-jackson.md index f7eaca74ce..00d6a45365 100644 --- a/akka-docs/src/main/paradox/serialization-jackson.md +++ b/akka-docs/src/main/paradox/serialization-jackson.md @@ -305,3 +305,21 @@ It's also possible to define several bindings and use different configuration fo different settings for remote messages and persisted events. @@snip [config](/akka-serialization-jackson/src/test/scala/doc/akka/serialization/jackson/SerializationDocSpec.scala) { #several-config } + +## Additional configuration + +Additional Jackson serialization features can be enabled/disabled in configuration. The default values from +Jackson are used aside from the the following that are changed in Akka's default configuration. + +@@snip [reference.conf](/akka-serialization-jackson/src/main/resources/reference.conf) { #features } + +### Date/time format + +`WRITE_DATES_AS_TIMESTAMPS` is 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. + +@@snip [config](/akka-serialization-jackson/src/test/scala/doc/akka/serialization/jackson/SerializationDocSpec.scala) { #date-time } + +Jackson is still be able to deserialize the other format independent of this setting. diff --git a/akka-serialization-jackson/src/main/resources/reference.conf b/akka-serialization-jackson/src/main/resources/reference.conf index 664db178c6..b1cefb42ea 100644 --- a/akka-serialization-jackson/src/main/resources/reference.conf +++ b/akka-serialization-jackson/src/main/resources/reference.conf @@ -44,15 +44,23 @@ akka.serialization.jackson { migrations { } +} + +#//#features +akka.serialization.jackson { # Configuration of the ObjectMapper serialization features. # See com.fasterxml.jackson.databind.SerializationFeature # Enum values corresponding to the SerializationFeature and their boolean value. serialization-features { - + # Date/time in ISO-8601 (rfc3339) yyyy-MM-dd'T'HH:mm:ss.SSSZ format + # as defined by com.fasterxml.jackson.databind.util.StdDateFormat + # 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 } # Configuration of the ObjectMapper deserialization features. - # See com.fasterxml.jackson.databind.SeserializationFeature + # See com.fasterxml.jackson.databind.DeserializationFeature # Enum values corresponding to the DeserializationFeature and their boolean value. deserialization-features { FAIL_ON_UNKNOWN_PROPERTIES = off @@ -71,6 +79,7 @@ akka.serialization.jackson { jackson-smile {} } +#//#features akka.actor { serializers { 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 8b1f90c7a8..806e022511 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 @@ -158,32 +158,54 @@ class JacksonJsonSerializerSpec extends JacksonSerializerSpec("jackson-json") { "JacksonJsonSerializer with Java message classes" must { import JavaTestMessages._ - // see SerializationFeature.WRITE_DATES_AS_TIMESTAMPS - "by default serialize dates and durations as numeric timestamps" in { + // see SerializationFeature.WRITE_DATES_AS_TIMESTAMPS = off + "by default serialize dates and durations as text with ISO-8601 date format" in { + // Default format is defined in com.fasterxml.jackson.databind.util.StdDateFormat + // ISO-8601 yyyy-MM-dd'T'HH:mm:ss.SSSZ (rfc3339) val msg = new TimeCommand(LocalDateTime.of(2019, 4, 29, 23, 15, 3, 12345), Duration.of(5, ChronoUnit.SECONDS)) val json = serializeToJsonString(msg) - val expected = """{"timestamp":[2019,4,29,23,15,3,12345],"duration":5.000000000}""" + val expected = """{"timestamp":"2019-04-29T23:15:03.000012345","duration":"PT5S"}""" json should ===(expected) + + // and full round trip + checkSerialization(msg) + + // and it can still deserialize from numeric timestamps format + val serializer = serializerFor(msg) + val manifest = serializer.manifest(msg) + val serializerId = serializer.identifier + val deserializedFromTimestampsFormat = deserializeFromJsonString( + """{"timestamp":[2019,4,29,23,15,3,12345],"duration":5.000000000}""", + serializerId, + manifest) + deserializedFromTimestampsFormat should ===(msg) } - // see SerializationFeature.WRITE_DATES_AS_TIMESTAMPS - "be possible to serialize dates and durations as text with default date format " in { + // see SerializationFeature.WRITE_DATES_AS_TIMESTAMPS = on + "be possible to serialize dates and durations as numeric timestamps" in { withSystem(""" akka.serialization.jackson.serialization-features { - WRITE_DATES_AS_TIMESTAMPS = off + WRITE_DATES_AS_TIMESTAMPS = on } """) { sys => val msg = new TimeCommand(LocalDateTime.of(2019, 4, 29, 23, 15, 3, 12345), Duration.of(5, ChronoUnit.SECONDS)) val json = serializeToJsonString(msg, sys) - // Default format is defined in com.fasterxml.jackson.databind.util.StdDateFormat - // ISO-8601 yyyy-MM-dd'T'HH:mm:ss.SSSZ - // FIXME is this the same as rfc3339, or do we need something else to support interop with the format used by Play JSON? - // FIXME should we make this the default rather than numberic timestamps? - val expected = """{"timestamp":"2019-04-29T23:15:03.000012345","duration":"PT5S"}""" + val expected = """{"timestamp":[2019,4,29,23,15,3,12345],"duration":5.000000000}""" json should ===(expected) // and full round trip - checkSerialization(msg) + checkSerialization(msg, sys) + + // and it can still deserialize from ISO format + val serializer = serializerFor(msg, sys) + val manifest = serializer.manifest(msg) + val serializerId = serializer.identifier + val deserializedFromIsoFormat = deserializeFromJsonString( + """{"timestamp":"2019-04-29T23:15:03.000012345","duration":"PT5S"}""", + serializerId, + manifest, + sys) + deserializedFromIsoFormat should ===(msg) } } diff --git a/akka-serialization-jackson/src/test/scala/doc/akka/serialization/jackson/SerializationDocSpec.scala b/akka-serialization-jackson/src/test/scala/doc/akka/serialization/jackson/SerializationDocSpec.scala index 009152e92a..289ffbb6cc 100644 --- a/akka-serialization-jackson/src/test/scala/doc/akka/serialization/jackson/SerializationDocSpec.scala +++ b/akka-serialization-jackson/src/test/scala/doc/akka/serialization/jackson/SerializationDocSpec.scala @@ -104,5 +104,12 @@ object SerializationDocSpec { final case class Elephant(name: String, age: Int) extends Animal //#polymorphism + val configDateTime = """ + #//#date-time + akka.serialization.jackson.serialization-features { + WRITE_DATES_AS_TIMESTAMPS = on + } + #//#date-time + """ } // FIXME add real tests for the migrations, see EventMigrationTest.java in Lagom