Skip to content

Commit ebf58cb

Browse files
xinlian12Copilot
andcommitted
feat: add BasicCustomItemSerializer for query tests (issue #45521)
Create a BasicCustomItemSerializer that mirrors the real-world use case from issue #45521 — a simple ObjectMapper-based serializer with custom settings (dates as ISO strings via JavaTimeModule) without transforming the document structure. Changes: - Add BasicCustomItemSerializer inner class with custom ObjectMapper - Add it to the Factory data provider so query tests also run with it - Update query tests to use the custom serializer directly when the serializer does not transform document structure (vs falling back to DEFAULT_SERIALIZER for envelope-wrapping) - Use instanceof checks for EnvelopWrappingItemSerializer instead of identity comparison for robustness Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 20bd377 commit ebf58cb

1 file changed

Lines changed: 67 additions & 17 deletions

File tree

sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/CosmosItemSerializerTest.java

Lines changed: 67 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@
3636
import com.fasterxml.jackson.core.JsonProcessingException;
3737
import com.fasterxml.jackson.databind.DeserializationFeature;
3838
import com.fasterxml.jackson.databind.ObjectMapper;
39+
import com.fasterxml.jackson.databind.SerializationFeature;
3940
import com.fasterxml.jackson.databind.node.ObjectNode;
41+
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
4042
import org.testng.annotations.AfterClass;
4143
import org.testng.annotations.BeforeClass;
4244
import org.testng.annotations.DataProvider;
@@ -93,7 +95,8 @@ public static Object[][] clientBuildersWithDirectSessionIncludeComputeGatewayAnd
9395
CosmosItemSerializer[] itemSerializers = new CosmosItemSerializer[] {
9496
null,
9597
CosmosItemSerializer.DEFAULT_SERIALIZER,
96-
EnvelopWrappingItemSerializer.INSTANCE_NO_TRACKING_ID_VALIDATION
98+
EnvelopWrappingItemSerializer.INSTANCE_NO_TRACKING_ID_VALIDATION,
99+
BasicCustomItemSerializer.INSTANCE
97100
};
98101

99102
List<Object[]> providers = new ArrayList<>();
@@ -323,11 +326,11 @@ private <T> void runPointOperationAndQueryTestCase(
323326
Class<T> classType) {
324327

325328
boolean useEnvelopeWrapper =
326-
requestLevelSerializer == EnvelopWrappingItemSerializer.INSTANCE_NO_TRACKING_ID_VALIDATION
329+
requestLevelSerializer instanceof EnvelopWrappingItemSerializer
327330
|| (requestLevelSerializer == null
328331
&& this.getClientBuilder()
329-
.getCustomItemSerializer() == EnvelopWrappingItemSerializer.INSTANCE_NO_TRACKING_ID_VALIDATION);
330-
if (requestLevelSerializer == EnvelopWrappingItemSerializer.INSTANCE_NO_TRACKING_ID_VALIDATION
332+
.getCustomItemSerializer() instanceof EnvelopWrappingItemSerializer);
333+
if (requestLevelSerializer instanceof EnvelopWrappingItemSerializer
331334
&& isContentOnWriteEnabled
332335
&& nonIdempotentWriteRetriesEnabled
333336
&& useTrackingIdForCreateAndReplace) {
@@ -704,6 +707,7 @@ public void queryWithOrderByAndCustomSerializer() {
704707
return;
705708
}
706709

710+
boolean isEnvelopeWrapper = clientSerializer instanceof EnvelopWrappingItemSerializer;
707711
String pkValue = UUID.randomUUID().toString();
708712
List<String> createdIds = new ArrayList<>();
709713
try {
@@ -748,6 +752,7 @@ public void queryWithAggregateAndCustomSerializer() {
748752
return;
749753
}
750754

755+
boolean isEnvelopeWrapper = clientSerializer instanceof EnvelopWrappingItemSerializer;
751756
String pkValue = UUID.randomUUID().toString();
752757
List<String> createdIds = new ArrayList<>();
753758
try {
@@ -760,14 +765,17 @@ public void queryWithAggregateAndCustomSerializer() {
760765
createdIds.add(id);
761766
}
762767

763-
// Use DEFAULT_SERIALIZER for the query request options because aggregate
768+
// For envelope-wrapping serializer, use DEFAULT_SERIALIZER because aggregate
764769
// results (e.g., COUNT) are not full documents and cannot be deserialized
765-
// by the envelope-wrapping serializer. The test still validates that the
766-
// client-level custom serializer does not leak into the internal query pipeline.
767-
// SELECT VALUE COUNT(1) returns a scalar integer, so use Integer.class.
770+
// by the envelope-wrapping serializer.
771+
// For BasicCustomItemSerializer, use the custom serializer directly since
772+
// it does not transform document structure.
768773
CosmosQueryRequestOptions queryRequestOptions = new CosmosQueryRequestOptions()
769-
.setCustomItemSerializer(CosmosItemSerializer.DEFAULT_SERIALIZER);
774+
.setCustomItemSerializer(isEnvelopeWrapper
775+
? CosmosItemSerializer.DEFAULT_SERIALIZER
776+
: clientSerializer);
770777

778+
// SELECT VALUE COUNT(1) returns a scalar integer, so use Integer.class.
771779
List<Integer> results = container
772780
.queryItems(
773781
"SELECT VALUE COUNT(1) FROM c WHERE c.mypk = '" + pkValue + "'",
@@ -794,6 +802,7 @@ public void queryWithDistinctAndCustomSerializer() {
794802
return;
795803
}
796804

805+
boolean isEnvelopeWrapper = clientSerializer instanceof EnvelopWrappingItemSerializer;
797806
String pkValue = UUID.randomUUID().toString();
798807
List<String> createdIds = new ArrayList<>();
799808
try {
@@ -806,12 +815,14 @@ public void queryWithDistinctAndCustomSerializer() {
806815
createdIds.add(id);
807816
}
808817

809-
// Use DEFAULT_SERIALIZER for the query request options because DISTINCT
818+
// For envelope-wrapping serializer, use DEFAULT_SERIALIZER because DISTINCT
810819
// projections are not full documents and cannot be deserialized by the
811-
// envelope-wrapping serializer. The test still validates that the client-level
812-
// custom serializer does not leak into the internal query pipeline.
820+
// envelope-wrapping serializer.
821+
// For BasicCustomItemSerializer, use the custom serializer directly.
813822
CosmosQueryRequestOptions queryRequestOptions = new CosmosQueryRequestOptions()
814-
.setCustomItemSerializer(CosmosItemSerializer.DEFAULT_SERIALIZER);
823+
.setCustomItemSerializer(isEnvelopeWrapper
824+
? CosmosItemSerializer.DEFAULT_SERIALIZER
825+
: clientSerializer);
815826

816827
List<ObjectNode> results = container
817828
.queryItems(
@@ -839,6 +850,7 @@ public void queryWithGroupByAndCustomSerializer() {
839850
return;
840851
}
841852

853+
boolean isEnvelopeWrapper = clientSerializer instanceof EnvelopWrappingItemSerializer;
842854
String pkValue = UUID.randomUUID().toString();
843855
List<String> createdIds = new ArrayList<>();
844856
try {
@@ -851,12 +863,14 @@ public void queryWithGroupByAndCustomSerializer() {
851863
createdIds.add(id);
852864
}
853865

854-
// Use DEFAULT_SERIALIZER for the query request options because GROUP BY
866+
// For envelope-wrapping serializer, use DEFAULT_SERIALIZER because GROUP BY
855867
// projections are not full documents and cannot be deserialized by the
856-
// envelope-wrapping serializer. The test still validates that the client-level
857-
// custom serializer does not leak into the internal query pipeline.
868+
// envelope-wrapping serializer.
869+
// For BasicCustomItemSerializer, use the custom serializer directly.
858870
CosmosQueryRequestOptions queryRequestOptions = new CosmosQueryRequestOptions()
859-
.setCustomItemSerializer(CosmosItemSerializer.DEFAULT_SERIALIZER);
871+
.setCustomItemSerializer(isEnvelopeWrapper
872+
? CosmosItemSerializer.DEFAULT_SERIALIZER
873+
: clientSerializer);
860874

861875
List<ObjectNode> results = container
862876
.queryItems(
@@ -1238,4 +1252,40 @@ public <T> T deserialize(Map<String, Object> jsonNodeMap, Class<T> classType) {
12381252
classType);
12391253
}
12401254
}
1255+
1256+
/**
1257+
* A simple custom item serializer that mirrors the real-world use case from
1258+
* <a href="https://github.com/Azure/azure-sdk-for-java/issues/45521">issue #45521</a>.
1259+
* Uses a custom ObjectMapper with different settings (e.g., dates as ISO strings)
1260+
* without transforming the document structure (no wrapping/unwrapping).
1261+
*/
1262+
@SuppressWarnings("unchecked")
1263+
private static class BasicCustomItemSerializer extends CosmosItemSerializer {
1264+
public static final BasicCustomItemSerializer INSTANCE = new BasicCustomItemSerializer();
1265+
1266+
private static final ObjectMapper customMapper = new ObjectMapper()
1267+
.registerModule(new JavaTimeModule())
1268+
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
1269+
.configure(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE, false)
1270+
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
1271+
1272+
private BasicCustomItemSerializer() {
1273+
}
1274+
1275+
@Override
1276+
public <T> Map<String, Object> serialize(T item) {
1277+
if (item == null) {
1278+
return null;
1279+
}
1280+
return customMapper.convertValue(item, Map.class);
1281+
}
1282+
1283+
@Override
1284+
public <T> T deserialize(Map<String, Object> jsonNodeMap, Class<T> classType) {
1285+
if (jsonNodeMap == null) {
1286+
return null;
1287+
}
1288+
return customMapper.convertValue(jsonNodeMap, classType);
1289+
}
1290+
}
12411291
}

0 commit comments

Comments
 (0)