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:
parent
16f4971f64
commit
9caae087a2
3 changed files with 212 additions and 17 deletions
|
|
@ -64,6 +64,36 @@ akka.serialization.jackson {
|
||||||
FAIL_ON_UNKNOWN_PROPERTIES = off
|
FAIL_ON_UNKNOWN_PROPERTIES = off
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Configuration of the ObjectMapper mapper features.
|
||||||
|
# See com.fasterxml.jackson.databind.MapperFeature
|
||||||
|
# Enum values corresponding to the MapperFeature and their
|
||||||
|
# boolean values, for example:
|
||||||
|
#
|
||||||
|
# mapper-features {
|
||||||
|
# SORT_PROPERTIES_ALPHABETICALLY = on
|
||||||
|
# }
|
||||||
|
mapper-features {}
|
||||||
|
|
||||||
|
# Configuration of the ObjectMapper JsonParser features.
|
||||||
|
# See com.fasterxml.jackson.core.JsonParser.Feature
|
||||||
|
# Enum values corresponding to the JsonParser.Feature and their
|
||||||
|
# boolean value, for example:
|
||||||
|
#
|
||||||
|
# json-parser-features {
|
||||||
|
# ALLOW_SINGLE_QUOTES = on
|
||||||
|
# }
|
||||||
|
json-parser-features {}
|
||||||
|
|
||||||
|
# Configuration of the ObjectMapper JsonParser features.
|
||||||
|
# See com.fasterxml.jackson.core.JsonGenerator.Feature
|
||||||
|
# Enum values corresponding to the JsonGenerator.Feature and
|
||||||
|
# their boolean value, for example:
|
||||||
|
#
|
||||||
|
# json-generator-features {
|
||||||
|
# WRITE_NUMBERS_AS_STRINGS = on
|
||||||
|
# }
|
||||||
|
json-generator-features {}
|
||||||
|
|
||||||
# Additional classes that are allowed even if they are not defined in `serialization-bindings`.
|
# 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
|
# 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.
|
# from `serialization-bindings`, but should still be possible to deserialize.
|
||||||
|
|
|
||||||
|
|
@ -28,11 +28,14 @@ import com.fasterxml.jackson.annotation.JsonCreator
|
||||||
import com.fasterxml.jackson.annotation.PropertyAccessor
|
import com.fasterxml.jackson.annotation.PropertyAccessor
|
||||||
import com.fasterxml.jackson.core.JsonFactory
|
import com.fasterxml.jackson.core.JsonFactory
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||||
|
import com.fasterxml.jackson.databind.MapperFeature
|
||||||
import com.fasterxml.jackson.databind.Module
|
import com.fasterxml.jackson.databind.Module
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature
|
import com.fasterxml.jackson.databind.SerializationFeature
|
||||||
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule
|
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule
|
||||||
import com.typesafe.config.Config
|
import com.typesafe.config.Config
|
||||||
|
import com.fasterxml.jackson.core.JsonParser
|
||||||
|
import com.fasterxml.jackson.core.JsonGenerator
|
||||||
|
|
||||||
object JacksonObjectMapperProvider extends ExtensionId[JacksonObjectMapperProvider] with ExtensionIdProvider {
|
object JacksonObjectMapperProvider extends ExtensionId[JacksonObjectMapperProvider] with ExtensionIdProvider {
|
||||||
override def get(system: ActorSystem): JacksonObjectMapperProvider = super.get(system)
|
override def get(system: ActorSystem): JacksonObjectMapperProvider = super.get(system)
|
||||||
|
|
@ -94,6 +97,32 @@ object JacksonObjectMapperProvider extends ExtensionId[JacksonObjectMapperProvid
|
||||||
case (feature, value) => mapper.configure(feature, value)
|
case (feature, value) => mapper.configure(feature, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val configuredMapperFeatures = features(config, "mapper-features").map {
|
||||||
|
case (enumName, value) => MapperFeature.valueOf(enumName) -> value
|
||||||
|
}
|
||||||
|
val mapperFeatures = objectMapperFactory.overrideConfiguredMapperFeatures(bindingName, configuredMapperFeatures)
|
||||||
|
mapperFeatures.foreach {
|
||||||
|
case (feature, value) => mapper.configure(feature, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
val configuredJsonParserFeatures = features(config, "json-parser-features").map {
|
||||||
|
case (enumName, value) => JsonParser.Feature.valueOf(enumName) -> value
|
||||||
|
}
|
||||||
|
val jsonParserFeatures =
|
||||||
|
objectMapperFactory.overrideConfiguredJsonParserFeatures(bindingName, configuredJsonParserFeatures)
|
||||||
|
jsonParserFeatures.foreach {
|
||||||
|
case (feature, value) => mapper.configure(feature, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
val configuredJsonGeneratorFeatures = features(config, "json-generator-features").map {
|
||||||
|
case (enumName, value) => JsonGenerator.Feature.valueOf(enumName) -> value
|
||||||
|
}
|
||||||
|
val jsonGeneratorFeatures =
|
||||||
|
objectMapperFactory.overrideConfiguredJsonGeneratorFeatures(bindingName, configuredJsonGeneratorFeatures)
|
||||||
|
jsonGeneratorFeatures.foreach {
|
||||||
|
case (feature, value) => mapper.configure(feature, value)
|
||||||
|
}
|
||||||
|
|
||||||
val configuredModules = config.getStringList("jackson-modules").asScala
|
val configuredModules = config.getStringList("jackson-modules").asScala
|
||||||
val modules1 =
|
val modules1 =
|
||||||
configuredModules.flatMap { fqcn =>
|
configuredModules.flatMap { fqcn =>
|
||||||
|
|
@ -327,4 +356,43 @@ class JacksonObjectMapperFactory {
|
||||||
configuredModules: immutable.Seq[Module]): immutable.Seq[Module] =
|
configuredModules: immutable.Seq[Module]): immutable.Seq[Module] =
|
||||||
configuredModules
|
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
|
||||||
|
* return the features that are to be applied to the `ObjectMapper`.
|
||||||
|
*
|
||||||
|
* @param bindingName bindingName name of this `ObjectMapper`
|
||||||
|
* @param configuredFeatures the list of `MapperFeatures` that were configured in `akka.serialization.jackson.mapper-features`
|
||||||
|
*/
|
||||||
|
def overrideConfiguredMapperFeatures(
|
||||||
|
@unused bindingName: String,
|
||||||
|
configuredFeatures: immutable.Seq[(MapperFeature, Boolean)]): immutable.Seq[(MapperFeature, Boolean)] =
|
||||||
|
configuredFeatures
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After construction of the `ObjectMapper` the configured JSON parser features are applied to
|
||||||
|
* the mapper. These features can be amended programmatically by overriding this method and
|
||||||
|
* return the features that are to be applied to the `ObjectMapper`.
|
||||||
|
*
|
||||||
|
* @param bindingName bindingName name of this `ObjectMapper`
|
||||||
|
* @param configuredFeatures the list of `JsonParser.Feature` that were configured in `akka.serialization.jackson.json-parser-features`
|
||||||
|
*/
|
||||||
|
def overrideConfiguredJsonParserFeatures(
|
||||||
|
@unused bindingName: String,
|
||||||
|
configuredFeatures: immutable.Seq[(JsonParser.Feature, Boolean)]): immutable.Seq[(JsonParser.Feature, Boolean)] =
|
||||||
|
configuredFeatures
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After construction of the `ObjectMapper` the configured JSON generator features are applied to
|
||||||
|
* the mapper. These features can be amended programmatically by overriding this method and
|
||||||
|
* return the features that are to be applied to the `ObjectMapper`.
|
||||||
|
*
|
||||||
|
* @param bindingName bindingName name of this `ObjectMapper`
|
||||||
|
* @param configuredFeatures the list of `JsonGenerator.Feature` that were configured in `akka.serialization.jackson.json-generator-features`
|
||||||
|
*/
|
||||||
|
def overrideConfiguredJsonGeneratorFeatures(
|
||||||
|
@unused bindingName: String,
|
||||||
|
configuredFeatures: immutable.Seq[(JsonGenerator.Feature, Boolean)])
|
||||||
|
: immutable.Seq[(JsonGenerator.Feature, Boolean)] =
|
||||||
|
configuredFeatures
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import akka.testkit.TestKit
|
||||||
import com.fasterxml.jackson.annotation.JsonSubTypes
|
import com.fasterxml.jackson.annotation.JsonSubTypes
|
||||||
import com.fasterxml.jackson.annotation.JsonTypeInfo
|
import com.fasterxml.jackson.annotation.JsonTypeInfo
|
||||||
import com.fasterxml.jackson.core.JsonFactory
|
import com.fasterxml.jackson.core.JsonFactory
|
||||||
|
import com.fasterxml.jackson.databind.MapperFeature
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||||
import com.fasterxml.jackson.databind.JsonNode
|
import com.fasterxml.jackson.databind.JsonNode
|
||||||
import com.fasterxml.jackson.databind.Module
|
import com.fasterxml.jackson.databind.Module
|
||||||
|
|
@ -45,6 +46,8 @@ import com.typesafe.config.ConfigFactory
|
||||||
import org.scalatest.BeforeAndAfterAll
|
import org.scalatest.BeforeAndAfterAll
|
||||||
import org.scalatest.Matchers
|
import org.scalatest.Matchers
|
||||||
import org.scalatest.WordSpecLike
|
import org.scalatest.WordSpecLike
|
||||||
|
import com.fasterxml.jackson.core.JsonParser
|
||||||
|
import com.fasterxml.jackson.core.JsonGenerator
|
||||||
|
|
||||||
object ScalaTestMessages {
|
object ScalaTestMessages {
|
||||||
trait TestMessage
|
trait TestMessage
|
||||||
|
|
@ -137,23 +140,84 @@ class JacksonJsonSerializerSpec extends JacksonSerializerSpec("jackson-json") {
|
||||||
JacksonObjectMapperProvider(system).getOrCreate(anotherBindingName, None) shouldBe theSameInstanceAs(mapper2)
|
JacksonObjectMapperProvider(system).getOrCreate(anotherBindingName, None) shouldBe theSameInstanceAs(mapper2)
|
||||||
}
|
}
|
||||||
|
|
||||||
"support several different configurations" in {
|
"JacksonSerializer configuration" must {
|
||||||
|
|
||||||
withSystem("""
|
withSystem("""
|
||||||
akka.actor.serializers.jackson-json2 = "akka.serialization.jackson.JacksonJsonSerializer"
|
akka.actor.serializers.jackson-json2 = "akka.serialization.jackson.JacksonJsonSerializer"
|
||||||
akka.actor.serialization-identifiers.jackson-json2 = 999
|
akka.actor.serialization-identifiers.jackson-json2 = 999
|
||||||
akka.serialization.jackson.jackson-json2 {
|
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
|
# on is Jackson's default
|
||||||
val objMapper =
|
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
|
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,
|
bindingName: String,
|
||||||
configuredFeatures: immutable.Seq[(SerializationFeature, Boolean)])
|
configuredFeatures: immutable.Seq[(SerializationFeature, Boolean)])
|
||||||
: immutable.Seq[(SerializationFeature, Boolean)] = {
|
: immutable.Seq[(SerializationFeature, Boolean)] = {
|
||||||
if (bindingName == "jackson-json") {
|
if (bindingName == "jackson-json")
|
||||||
configuredFeatures :+ (SerializationFeature.INDENT_OUTPUT -> true)
|
configuredFeatures :+ (SerializationFeature.INDENT_OUTPUT -> true)
|
||||||
} else
|
else
|
||||||
super.overrideConfiguredSerializationFeatures(bindingName, configuredFeatures)
|
super.overrideConfiguredSerializationFeatures(bindingName, configuredFeatures)
|
||||||
}
|
}
|
||||||
|
|
||||||
override def overrideConfiguredModules(
|
override def overrideConfiguredModules(
|
||||||
bindingName: String,
|
bindingName: String,
|
||||||
configuredModules: immutable.Seq[Module]): immutable.Seq[Module] =
|
configuredModules: immutable.Seq[Module]): immutable.Seq[Module] =
|
||||||
if (bindingName == "jackson-json") {
|
if (bindingName == "jackson-json")
|
||||||
configuredModules.filterNot(_.isInstanceOf[JavaTimeModule])
|
configuredModules.filterNot(_.isInstanceOf[JavaTimeModule])
|
||||||
} else
|
else
|
||||||
super.overrideConfiguredModules(bindingName, configuredModules)
|
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
|
val config = system.settings.config
|
||||||
|
|
@ -275,12 +365,19 @@ class JacksonJsonSerializerSpec extends JacksonSerializerSpec("jackson-json") {
|
||||||
.withSetup(JacksonObjectMapperProviderSetup(customJacksonObjectMapperFactory))
|
.withSetup(JacksonObjectMapperProviderSetup(customJacksonObjectMapperFactory))
|
||||||
.withSetup(BootstrapSetup(config))
|
.withSetup(BootstrapSetup(config))
|
||||||
withSystem(setup) { sys =>
|
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 msg = InstantCommand(Instant.ofEpochMilli(1559907792075L))
|
||||||
val json = serializeToJsonString(msg, sys)
|
val json = serializeToJsonString(msg, sys)
|
||||||
// using the custom ObjectMapper with pretty printing enabled, and no JavaTimeModule
|
// using the custom ObjectMapper with pretty printing enabled, and no JavaTimeModule
|
||||||
json should include(""" "instant" : {""")
|
json should include(""" "instant" : {""")
|
||||||
json should include(""" "seconds" : 1559907792,""")
|
json should include(""" "nanos" : "75000000",""")
|
||||||
json should include(""" "nanos" : 75000000,""")
|
json should include(""" "seconds" : "1559907792"""")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue