diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/CosmosItemSerializerUnitTests.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/CosmosItemSerializerUnitTests.java index 82cc7bf9f213..ac866c4c615f 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/CosmosItemSerializerUnitTests.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/CosmosItemSerializerUnitTests.java @@ -12,6 +12,7 @@ import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; +import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -197,4 +198,44 @@ private static class PojoItem public String nullValueProperty = null; public String emptyValueProperty = ""; } + + @Test(groups = { "unit" }) + public void defaultSerializerWritesInstantAsIso8601String() { + ObjectMapper mapper = Utils.getDocumentObjectMapper(Configs.getItemSerializationInclusionMode()); + CosmosItemSerializer serializer = new DefaultCosmosItemSerializer(mapper); + + Instant createdAt = Instant.parse("2023-07-22T09:46:40.123456789Z"); + PojoItemWithInstant item = new PojoItemWithInstant(); + item.createdAt = createdAt; + + Map jsonMap = serializer.serialize(item); + ByteBuffer buffer = Utils.serializeJsonToByteBuffer(serializer, jsonMap, null, false); + String json = new String(buffer.array(), 0, buffer.limit(), StandardCharsets.UTF_8); + + logger.info("Actual: {}", json); + assertThat(json).isEqualTo( + "{\"id\":\"SomeId\",\"createdAt\":\"" + createdAt + "\"}"); + } + + @Test(groups = { "unit" }) + public void defaultSerializerRoundTripsInstant() { + ObjectMapper mapper = Utils.getDocumentObjectMapper(Configs.getItemSerializationInclusionMode()); + CosmosItemSerializer serializer = new DefaultCosmosItemSerializer(mapper); + + Instant createdAt = Instant.parse("2023-07-22T09:46:40.123456789Z"); + PojoItemWithInstant item = new PojoItemWithInstant(); + item.createdAt = createdAt; + + Map jsonMap = serializer.serialize(item); + PojoItemWithInstant deserialized = serializer.deserialize(jsonMap, PojoItemWithInstant.class); + + assertThat(deserialized.id).isEqualTo(item.id); + assertThat(deserialized.createdAt).isEqualTo(createdAt); + } + + private static class PojoItemWithInstant + { + public String id = "SomeId"; + public Instant createdAt; + } } diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/UtilsTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/UtilsTest.java index 0ed8473cd80a..fb9ab7374871 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/UtilsTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/UtilsTest.java @@ -9,9 +9,11 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationConfig; +import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.module.afterburner.AfterburnerModule; import com.fasterxml.jackson.module.blackbird.BlackbirdModule; +import java.time.Instant; import java.util.List; import java.util.stream.Collectors; import org.testng.annotations.Test; @@ -165,4 +167,37 @@ public void serializationInclusionMode() { .isEqualTo( JsonInclude.Value.construct(JsonInclude.Include.NON_DEFAULT, JsonInclude.Include.NON_DEFAULT)); } + + @Test(groups = { "unit" }) + public void documentObjectMapperDoesNotWriteDatesAsTimestamps() { + assertThat( + Utils.getSimpleObjectMapper() + .getSerializationConfig() + .isEnabled(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)) + .isFalse(); + + assertThat( + Utils.getDocumentObjectMapper(null) + .getSerializationConfig() + .isEnabled(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)) + .isFalse(); + + assertThat( + Utils.getDocumentObjectMapper("Always") + .getSerializationConfig() + .isEnabled(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)) + .isFalse(); + } + + @Test(groups = { "unit" }) + public void instantSerializesAsIso8601String() throws Exception { + Instant instant = Instant.parse("2023-07-22T09:46:40.123456789Z"); + + String json = Utils.getSimpleObjectMapper().writeValueAsString(instant); + + assertThat(json).isEqualTo("\"" + instant.toString() + "\""); + + Instant roundTripped = Utils.getSimpleObjectMapper().readValue(json, Instant.class); + assertThat(roundTripped).isEqualTo(instant); + } } diff --git a/sdk/cosmos/azure-cosmos/CHANGELOG.md b/sdk/cosmos/azure-cosmos/CHANGELOG.md index 7f3b456f7c32..bcce6ef836a7 100644 --- a/sdk/cosmos/azure-cosmos/CHANGELOG.md +++ b/sdk/cosmos/azure-cosmos/CHANGELOG.md @@ -5,6 +5,7 @@ #### Features Added #### Breaking Changes +* Fixed `java.time.Instant` (and other `java.time` types handled by `JavaTimeModule`) serializing as a numeric epoch timestamp instead of an ISO-8601 string when using the default `CosmosItemSerializer`. Items previously written with epoch-numeric date fields will now be written with ISO-8601 string values going forward; reading previously-stored epoch-numeric values back into `Instant` fields continues to work unaffected. - See PR [49720](https://github.com/Azure/azure-sdk-for-java/pull/49720). #### Bugs Fixed * Unified request-level consistency override behavior across transports: invalid attempts to upgrade the request consistency level above the account default are now silently ignored instead of returning `BadRequest` in some gateway paths. - See PR [49606](https://github.com/Azure/azure-sdk-for-java/pull/49606). diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/Utils.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/Utils.java index a387e72a64ce..21abc9999af0 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/Utils.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/Utils.java @@ -18,6 +18,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; @@ -152,6 +153,7 @@ private static ObjectMapper createAndInitializeObjectMapper(boolean allowDuplica tryToLoadJacksonPerformanceLibrary(objectMapper); objectMapper.registerModule(new JavaTimeModule()); + objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); return objectMapper; }