Skip to content

Commit

Permalink
SystemTextJsonSerializer: Refactors Code to Introduce `SystemTextJson…
Browse files Browse the repository at this point in the history
…SerializerOptions` to Set the Default STJ Serializer (#4589)

* Code changes to make STJ serializer public for preview.

* Code changes to make STJ serializer public for GA.

* Code changes to hide STJ serializer implementation behind a boolean flag.

* Code changes to fix tests.

* Revert "Code changes to fix tests."

This reverts commit c1ee171.

* Code changes to fix baseline test.

* Code changes to fix root cause.

* Code changes to update tests.

* Code changes to address review comments.

* Code changes to add serializer options as contract.

* Code changes to add serializer options as public contract in builder.

* Code changes to update contract to remove the STJ serializer boolean flag.

* Code changes to update summary.

* Code changes for minor cosmetic update.

* Code changes to fix xml comment.

* Code changes to move remarks to summary. Updated test.

* Code changes to move the validation logic in cosmos client options.

* Code changes to update contract.

* Code changes to remove client context core logic in cosmos client options.

* Code changes to update some tests.

* Code changes to update contract. Made cosmetic changes.
  • Loading branch information
kundadebdatta authored Jul 30, 2024
1 parent 7d7269b commit 908f037
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 14 deletions.
49 changes: 44 additions & 5 deletions Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ public class CosmosClientOptions
/// </summary>
private int gatewayModeMaxConnectionLimit;
private CosmosSerializationOptions serializerOptions;
private CosmosSerializer serializerInternal;
private CosmosSerializer serializerInternal;
private System.Text.Json.JsonSerializerOptions stjSerializerOptions;

private ConnectionMode connectionMode;
private Protocol connectionProtocol;
Expand Down Expand Up @@ -392,6 +393,44 @@ public ConnectionMode ConnectionMode
/// <seealso cref="ItemRequestOptions.EnableContentResponseOnWrite"/>
/// <seealso cref="TransactionalBatchItemRequestOptions.EnableContentResponseOnWrite"/>
public bool? EnableContentResponseOnWrite { get; set; }

/// <summary>
/// Sets the <see cref="System.Text.Json.JsonSerializerOptions"/> for the System.Text.Json serializer.
/// Note that if this option is provided, then the SDK will use the System.Text.Json as the default serializer and set
/// the serializer options as the constructor args.
/// </summary>
/// <example>
/// An example on how to configure the System.Text.Json serializer options to ignore null values
/// <code language="c#">
/// <![CDATA[
/// CosmosClientOptions clientOptions = new CosmosClientOptions()
/// {
/// UseSystemTextJsonSerializerWithOptions = new System.Text.Json.JsonSerializerOptions()
/// {
/// DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,
/// }
/// };
///
/// CosmosClient client = new CosmosClient("endpoint", "key", clientOptions);
/// ]]>
/// </code>
/// </example>
public System.Text.Json.JsonSerializerOptions UseSystemTextJsonSerializerWithOptions
{
get => this.stjSerializerOptions;
set
{
if (this.Serializer != null || this.SerializerOptions != null)
{
throw new ArgumentException(
$"{nameof(this.UseSystemTextJsonSerializerWithOptions)} is not compatible with {nameof(this.Serializer)} or {nameof(this.SerializerOptions)}. Only one can be set. ");
}

this.stjSerializerOptions = value;
this.serializerInternal = new CosmosSystemTextJsonSerializer(
this.stjSerializerOptions);
}
}

/// <summary>
/// Gets or sets the advanced replica selection flag. The advanced replica selection logic keeps track of the replica connection
Expand Down Expand Up @@ -543,10 +582,10 @@ public CosmosSerializationOptions SerializerOptions
get => this.serializerOptions;
set
{
if (this.Serializer != null)
if (this.Serializer != null || this.UseSystemTextJsonSerializerWithOptions != null)
{
throw new ArgumentException(
$"{nameof(this.SerializerOptions)} is not compatible with {nameof(this.Serializer)}. Only one can be set. ");
$"{nameof(this.SerializerOptions)} is not compatible with {nameof(this.Serializer)} or {nameof(this.UseSystemTextJsonSerializerWithOptions)}. Only one can be set. ");
}

this.serializerOptions = value;
Expand Down Expand Up @@ -578,10 +617,10 @@ public CosmosSerializer Serializer
get => this.serializerInternal;
set
{
if (this.SerializerOptions != null)
if (this.SerializerOptions != null || this.UseSystemTextJsonSerializerWithOptions != null)
{
throw new ArgumentException(
$"{nameof(this.Serializer)} is not compatible with {nameof(this.SerializerOptions)}. Only one can be set. ");
$"{nameof(this.Serializer)} is not compatible with {nameof(this.SerializerOptions)} or {nameof(this.UseSystemTextJsonSerializerWithOptions)}. Only one can be set. ");
}

this.serializerInternal = value;
Expand Down
19 changes: 17 additions & 2 deletions Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -646,7 +646,22 @@ public CosmosClientBuilder WithContentResponseOnWrite(bool contentResponseOnWrit
{
this.clientOptions.EnableContentResponseOnWrite = contentResponseOnWrite;
return this;
}
}

/// <summary>
/// Configures the <see cref="CosmosClientBuilder"/> to use System.Text.Json for serialization.
/// Use <see cref="System.Text.Json.JsonSerializerOptions" /> to use System.Text.Json with a default configuration.
/// If no options are specified, Newtonsoft.Json will be used for serialization instead.
/// </summary>
/// <param name="serializerOptions">An instance of <see cref="System.Text.Json.JsonSerializerOptions"/>
/// containing the system text json serializer options.</param>
/// <returns>The <see cref="CosmosClientBuilder"/> object</returns>
public CosmosClientBuilder WithSystemTextJsonSerializerOptions(
System.Text.Json.JsonSerializerOptions serializerOptions)
{
this.clientOptions.UseSystemTextJsonSerializerWithOptions = serializerOptions;
return this;
}

/// <summary>
/// The event handler to be invoked before the request is sent.
Expand Down Expand Up @@ -725,7 +740,7 @@ internal CosmosClientBuilder WithPartitionLevelFailoverEnabled()
{
this.clientOptions.EnablePartitionLevelFailover = true;
return this;
}
}

/// <summary>
/// Enables SDK to inject fault. Used for testing applications.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ public class LinqAggregateCustomSerializationBaseline : BaselineTests<LinqAggreg
private static Cosmos.Database testDb;
private static Container testContainer;

private static CosmosSerializer stjCosmosSerializer;
private static CosmosClient stjClient;
private static Cosmos.Database testDbSTJ;
private static Container testContainerSTJ;
Expand Down Expand Up @@ -64,10 +63,10 @@ public async static Task Initialize(TestContext textContext)
testDb = await client.CreateDatabaseAsync(dbName);
testContainer = testDb.CreateContainerAsync(new ContainerProperties(id: Guid.NewGuid().ToString(), partitionKeyPath: "/Pk")).Result;

stjCosmosSerializer = new CosmosSystemTextJsonSerializer(new JsonSerializerOptions());

stjClient = TestCommon.CreateCosmosClient((cosmosClientBuilder)
=> cosmosClientBuilder.WithCustomSerializer(stjCosmosSerializer));
=> cosmosClientBuilder.WithSystemTextJsonSerializerOptions(
new JsonSerializerOptions()),
useCustomSeralizer: false);

// Set a callback to get the handle of the last executed query to do the verification
// This is neede because aggregate queries return type is a scalar so it can't be used
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ public async static Task Initialize(TestContext textContext)
TestDb = await CosmosClient.CreateDatabaseAsync(dbName);

CosmosDefaultSTJClient = TestCommon.CreateCosmosClient((cosmosClientBuilder)
=> cosmosClientBuilder.WithCustomSerializer(new CosmosSystemTextJsonSerializer(new JsonSerializerOptions())));
=> cosmosClientBuilder
.WithSystemTextJsonSerializerOptions(
new JsonSerializerOptions()),
useCustomSeralizer: false);

string dbNameSTJ = $"{nameof(LinqTranslationBaselineTests)}-{Guid.NewGuid():N}";
TestDbSTJDefault = await CosmosDefaultSTJClient.CreateDatabaseAsync(dbNameSTJ);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2988,6 +2988,16 @@
],
"MethodInfo": "System.String get_ApplicationRegion();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;"
},
"System.Text.Json.JsonSerializerOptions get_UseSystemTextJsonSerializerWithOptions()": {
"Type": "Method",
"Attributes": [],
"MethodInfo": "System.Text.Json.JsonSerializerOptions get_UseSystemTextJsonSerializerWithOptions();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;"
},
"System.Text.Json.JsonSerializerOptions UseSystemTextJsonSerializerWithOptions": {
"Type": "Property",
"Attributes": [],
"MethodInfo": "System.Text.Json.JsonSerializerOptions UseSystemTextJsonSerializerWithOptions;CanRead:True;CanWrite:True;System.Text.Json.JsonSerializerOptions get_UseSystemTextJsonSerializerWithOptions();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_UseSystemTextJsonSerializerWithOptions(System.Text.Json.JsonSerializerOptions);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;"
},
"System.TimeSpan get_RequestTimeout()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": {
"Type": "Method",
"Attributes": [
Expand Down Expand Up @@ -3165,6 +3175,11 @@
],
"MethodInfo": "Void set_TokenCredentialBackgroundRefreshInterval(System.Nullable`1[System.TimeSpan]);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;"
},
"Void set_UseSystemTextJsonSerializerWithOptions(System.Text.Json.JsonSerializerOptions)": {
"Type": "Method",
"Attributes": [],
"MethodInfo": "Void set_UseSystemTextJsonSerializerWithOptions(System.Text.Json.JsonSerializerOptions);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;"
},
"Void set_WebProxy(System.Net.IWebProxy)": {
"Type": "Method",
"Attributes": [],
Expand Down Expand Up @@ -4737,6 +4752,11 @@
"Attributes": [],
"MethodInfo": "Microsoft.Azure.Cosmos.Fluent.CosmosClientBuilder WithSerializerOptions(Microsoft.Azure.Cosmos.CosmosSerializationOptions);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;"
},
"Microsoft.Azure.Cosmos.Fluent.CosmosClientBuilder WithSystemTextJsonSerializerOptions(System.Text.Json.JsonSerializerOptions)": {
"Type": "Method",
"Attributes": [],
"MethodInfo": "Microsoft.Azure.Cosmos.Fluent.CosmosClientBuilder WithSystemTextJsonSerializerOptions(System.Text.Json.JsonSerializerOptions);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;"
},
"Microsoft.Azure.Cosmos.Fluent.CosmosClientBuilder WithThrottlingRetryOptions(System.TimeSpan, Int32)": {
"Type": "Method",
"Attributes": [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ namespace Microsoft.Azure.Cosmos.Tests
using System.Net.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using global::Azure.Core;
using Microsoft.Azure.Cosmos.Fluent;
using Microsoft.Azure.Documents;
Expand Down Expand Up @@ -476,6 +477,26 @@ public void UserAgentContainsEnvironmentInformation()
Assert.AreEqual(userAgentSuffix, connectionPolicy.UserAgentSuffix);
Assert.IsTrue(connectionPolicy.UserAgentContainer.UserAgent.StartsWith(expectedValue));
Assert.IsTrue(connectionPolicy.UserAgentContainer.UserAgent.EndsWith(userAgentSuffix));
}

[TestMethod]
public void ValidateThatCustomSerializerGetsOverriddenWhenSTJSerializerEnabled()
{
CosmosClientOptions options = new CosmosClientOptions()
{
UseSystemTextJsonSerializerWithOptions = new System.Text.Json.JsonSerializerOptions()
{
PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase,
}
};

CosmosClient client = new(
"https://fake-account.documents.azure.com:443/",
Convert.ToBase64String(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())),
options
);

Assert.AreEqual(typeof(CosmosSystemTextJsonSerializer), client.ClientOptions.Serializer.GetType());
}

[TestMethod]
Expand All @@ -500,8 +521,58 @@ public void ThrowOnCustomSerializerWithSerializerOptions()
};

options.Serializer = new CosmosJsonDotNetSerializer();
}

}

[TestMethod]
[DataRow(false, DisplayName = "Test when the client options order is maintained")]
[DataRow(true, DisplayName = "Test when the client options order is reversed")]
[ExpectedException(typeof(ArgumentException))]
public void ThrowOnCustomSerializerWithSTJSerializerEnabled(
bool reverseOrder)
{
if (reverseOrder)
{
CosmosClientOptions options = new CosmosClientOptions()
{
Serializer = new CosmosJsonDotNetSerializer(),
UseSystemTextJsonSerializerWithOptions = new System.Text.Json.JsonSerializerOptions(),
};
}
else
{
CosmosClientOptions options = new CosmosClientOptions()
{
UseSystemTextJsonSerializerWithOptions = new System.Text.Json.JsonSerializerOptions(),
Serializer = new CosmosJsonDotNetSerializer(),
};
}
}

[TestMethod]
[DataRow(false, DisplayName = "Test when the client options order is maintained")]
[DataRow(true, DisplayName = "Test when the client options order is reversed")]
[ExpectedException(typeof(ArgumentException))]
public void ThrowOnSerializerOptionsWithSTJSerializerEnabled(
bool reverseOrder)
{
if (reverseOrder)
{
CosmosClientOptions options = new CosmosClientOptions()
{
SerializerOptions = new CosmosSerializationOptions(),
UseSystemTextJsonSerializerWithOptions = new System.Text.Json.JsonSerializerOptions(),
};
}
else
{
CosmosClientOptions options = new CosmosClientOptions()
{
UseSystemTextJsonSerializerWithOptions = new System.Text.Json.JsonSerializerOptions(),
SerializerOptions = new CosmosSerializationOptions(),
};
}
}

[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void ThrowOnNullTokenCredential()
Expand Down

0 comments on commit 908f037

Please sign in to comment.