use ISO-8601 date/time format in Jackson serializer, #24155
* better for interoperability * deserialization from both formats are supported
This commit is contained in:
parent
cca13bdb32
commit
41600d3079
5 changed files with 138 additions and 19 deletions
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue