Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enabling serialization customization through CosmosSerializerOptions #650

Merged
merged 29 commits into from
Aug 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
e25d205
Made JsonSerializerSettings internal. Exposed a new constructor with …
Aug 7, 2019
2adf438
Fixed contract UT and updated changelog
Aug 7, 2019
82f1b4a
Made CosmosJsonDotNetSerializer internal. Added CosmosSerializerOptio…
Aug 7, 2019
8077e8e
Fixed naming
Aug 7, 2019
87b9993
Updating file name and changelog
Aug 7, 2019
885b5dc
Merge remote-tracking branch 'origin/master' into users/jawilley/seri…
Aug 7, 2019
e121a93
Update contract test
Aug 7, 2019
530b883
Fixed spacing
Aug 7, 2019
52dd1c7
Fixing contract enforcement test
Aug 8, 2019
18e50c2
Updated naming
Aug 8, 2019
b3ef7a4
Additional contract checks
Aug 8, 2019
7413ec0
Merge to latest
Aug 8, 2019
b8697d0
Adding additional info to ValidateAsync test to understand transient …
Aug 8, 2019
d9cc7fd
Updating contract from VS2017
Aug 8, 2019
c220603
Fixed contract test for VS2019 to skip is IsReadOnlyAttribute
Aug 8, 2019
e17ba0b
Update Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializerOptions.cs
j82w Aug 8, 2019
a90ff94
Update Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializerOptions.cs
j82w Aug 8, 2019
fd468ef
Update Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializerOptions.cs
j82w Aug 8, 2019
e176b82
Renamed CosmosSerializerOptions to CosmosSerializationOptions
Aug 8, 2019
3cdb52b
Merge to latest
Aug 8, 2019
5de82a6
Added more comments
Aug 8, 2019
febd523
Reverting DocumentClientWithUriParameters changes
Aug 9, 2019
c43f7a8
Revert "Reverting DocumentClientWithUriParameters changes"
Aug 9, 2019
3151f60
Reverting accidental changes
Aug 9, 2019
c7ac8db
Adding comment examples, Converted options to be nullable
Aug 9, 2019
9a340fd
Updated contract test
Aug 9, 2019
fa1c98c
Fixed Unit test
Aug 9, 2019
02185ef
Converted to a class with default values and updated changelog
Aug 9, 2019
5fa508e
Updated contract api
Aug 9, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions Microsoft.Azure.Cosmos/src/CosmosClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ protected CosmosClient()
/// <seealso href="https://docs.microsoft.com/azure/cosmos-db/troubleshoot-dot-net-sdk"/>
/// </remarks>
public CosmosClient(
string connectionString,
string connectionString,
j82w marked this conversation as resolved.
Show resolved Hide resolved
CosmosClientOptions clientOptions = null)
: this(
CosmosClientOptions.GetAccountEndpoint(connectionString),
Expand Down Expand Up @@ -612,18 +612,19 @@ internal void Init(

this.RequestHandler = clientPipelineBuilder.Build();

CosmosSerializer userSerializer = this.ClientOptions.GetCosmosSerializerWithWrapperOrDefault();
this.ResponseFactory = new CosmosResponseFactory(
defaultJsonSerializer: this.ClientOptions.PropertiesSerializer,
userJsonSerializer: this.ClientOptions.CosmosSerializerWithWrapperOrDefault);
userJsonSerializer: userSerializer);

CosmosSerializer sqlQuerySpecSerializer = CosmosSqlQuerySpecJsonConverter.CreateSqlQuerySpecSerializer(
this.ClientOptions.CosmosSerializerWithWrapperOrDefault,
this.ClientOptions.PropertiesSerializer);
cosmosSerializer: userSerializer,
propertiesSerializer: this.ClientOptions.PropertiesSerializer);

this.ClientContext = new ClientContextCore(
client: this,
clientOptions: this.ClientOptions,
userJsonSerializer: this.ClientOptions.CosmosSerializerWithWrapperOrDefault,
userJsonSerializer: userSerializer,
defaultJsonSerializer: this.ClientOptions.PropertiesSerializer,
sqlQuerySpecSerializer: sqlQuerySpecSerializer,
cosmosResponseFactory: this.ResponseFactory,
Expand All @@ -632,7 +633,7 @@ internal void Init(
documentQueryClient: new DocumentQueryClient(this.DocumentClient));
}

internal async virtual Task<ConsistencyLevel> GetAccountConsistencyLevelAsync()
internal virtual async Task<ConsistencyLevel> GetAccountConsistencyLevelAsync()
{
if (!this.accountConsistencyLevel.HasValue)
{
Expand Down
96 changes: 77 additions & 19 deletions Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ namespace Microsoft.Azure.Cosmos
using System.Collections.ObjectModel;
using System.Data.Common;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using Microsoft.Azure.Cosmos.Fluent;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
Expand All @@ -17,6 +16,18 @@ namespace Microsoft.Azure.Cosmos
/// <summary>
/// Defines all the configurable options that the CosmosClient requires.
/// </summary>
/// <example>
/// An example on how to configure the serialization option to ignore null values
/// CosmosClientOptions clientOptions = new CosmosClientOptions()
/// {
/// SerializerOptions = new CosmosSerializationOptions(){
/// IgnoreNullValues = true
/// },
/// ConnectionMode = ConnectionMode.Gateway,
/// };
///
/// CosmosClient client = new CosmosClient("endpoint", "key", clientOptions);
/// </example>
public class CosmosClientOptions
{
/// <summary>
Expand All @@ -39,11 +50,12 @@ public class CosmosClientOptions
/// </summary>
private static readonly CosmosSerializer propertiesSerializer = new CosmosJsonSerializerWrapper(new CosmosJsonDotNetSerializer());

private readonly Collection<RequestHandler> customHandlers;
private readonly string currentEnvironmentInformation;

private int gatewayModeMaxConnectionLimit;
private string applicationName;
private CosmosSerializationOptions serializerOptions;
private CosmosSerializer serializer;

/// <summary>
/// Creates a new CosmosClientOptions
Expand All @@ -59,7 +71,7 @@ public CosmosClientOptions()
this.ConnectionMode = CosmosClientOptions.DefaultConnectionMode;
this.ConnectionProtocol = CosmosClientOptions.DefaultProtocol;
this.ApiType = CosmosClientOptions.DefaultApiType;
this.customHandlers = new Collection<RequestHandler>();
this.CustomHandlers = new Collection<RequestHandler>();
j82w marked this conversation as resolved.
Show resolved Hide resolved
}

/// <summary>
Expand Down Expand Up @@ -132,10 +144,7 @@ public int GatewayModeMaxConnectionLimit
/// </summary>
/// <seealso cref="CosmosClientBuilder.AddCustomHandlers(RequestHandler[])"/>
[JsonConverter(typeof(ClientOptionJsonConverter))]
public Collection<RequestHandler> CustomHandlers
{
get => this.customHandlers;
}
public Collection<RequestHandler> CustomHandlers { get; }

/// <summary>
/// Get or set the connection mode used by the client when connecting to the Azure Cosmos DB service.
Expand All @@ -157,7 +166,7 @@ public Collection<RequestHandler> CustomHandlers
public ConsistencyLevel? ConsistencyLevel { get; set; }

/// <summary>
/// Get ot set the number of times client should retry on rate throttled requests.
/// Get or set the number of times client should retry on rate throttled requests.
/// </summary>
/// <seealso cref="CosmosClientBuilder.WithThrottlingRetryOptions(TimeSpan, int)"/>
public int? MaxRetryAttemptsOnRateLimitedRequests { get; set; }
Expand All @@ -171,6 +180,35 @@ public Collection<RequestHandler> CustomHandlers
/// <seealso cref="CosmosClientBuilder.WithThrottlingRetryOptions(TimeSpan, int)"/>
public TimeSpan? MaxRetryWaitTimeOnRateLimitedRequests { get; set; }

/// <summary>
/// Get to set optional serializer options.
/// </summary>
/// <example>
/// An example on how to configure the serialization option to ignore null values
/// CosmosClientOptions clientOptions = new CosmosClientOptions()
/// {
/// SerializerOptions = new CosmosSerializationOptions(){
/// IgnoreNullValues = true
/// }
/// };
///
/// CosmosClient client = new CosmosClient("endpoint", "key", clientOptions);
/// </example>
public CosmosSerializationOptions SerializerOptions
{
get => this.serializerOptions;
set
{
if (this.Serializer != null)
{
throw new ArgumentException(
$"{nameof(this.SerializerOptions)} is not compatible with {nameof(this.Serializer)}. Only one can be set. ");
}

this.serializerOptions = value;
}
}

/// <summary>
/// Get to set an optional JSON serializer. The client will use it to serialize or de-serialize user's cosmos request/responses.
/// SDK owned types such as DatabaseProperties and ContainerProperties will always use the SDK default serializer.
Expand All @@ -182,10 +220,7 @@ public Collection<RequestHandler> CustomHandlers
/// <example>
/// // An example on how to configure the serializer to ignore null values
/// CosmosSerializer ignoreNullSerializer = new CosmosJsonDotNetSerializer(
/// new JsonSerializerSettings()
/// {
/// NullValueHandling = NullValueHandling.Ignore
/// });
/// NullValueHandling = NullValueHandling.Ignore);
///
/// CosmosClientOptions clientOptions = new CosmosClientOptions()
/// {
Expand All @@ -195,7 +230,20 @@ public Collection<RequestHandler> CustomHandlers
/// CosmosClient client = new CosmosClient("endpoint", "key", clientOptions);
/// </example>
[JsonConverter(typeof(ClientOptionJsonConverter))]
public CosmosSerializer Serializer { get; set; }
public CosmosSerializer Serializer
{
get => this.serializer;
set
{
if (this.SerializerOptions != null)
{
throw new ArgumentException(
$"{nameof(this.Serializer)} is not compatible with {nameof(this.SerializerOptions)}. Only one can be set. ");
}

this.serializer = value;
}
}

/// <summary>
/// A JSON serializer used by the CosmosClient to serialize or de-serialize cosmos request/responses.
Expand All @@ -205,12 +253,6 @@ public Collection<RequestHandler> CustomHandlers
[JsonConverter(typeof(ClientOptionJsonConverter))]
internal CosmosSerializer PropertiesSerializer => CosmosClientOptions.propertiesSerializer;

/// <summary>
/// Gets the user json serializer with the CosmosJsonSerializerWrapper or the default
/// </summary>
[JsonIgnore]
internal CosmosSerializer CosmosSerializerWithWrapperOrDefault => this.Serializer == null ? this.PropertiesSerializer : new CosmosJsonSerializerWrapper(this.Serializer);

/// <summary>
/// Gets or sets the connection protocol when connecting to the Azure Cosmos service.
/// </summary>
Expand Down Expand Up @@ -309,6 +351,22 @@ public Collection<RequestHandler> CustomHandlers
/// </summary>
internal bool? EnableCpuMonitor { get; set; }

/// <summary>
/// Gets the user json serializer with the CosmosJsonSerializerWrapper or the default
/// </summary>
internal CosmosSerializer GetCosmosSerializerWithWrapperOrDefault()
{
if (this.SerializerOptions != null)
{
CosmosJsonDotNetSerializer cosmosJsonDotNetSerializer = new CosmosJsonDotNetSerializer(this.SerializerOptions);
return new CosmosJsonSerializerWrapper(cosmosJsonDotNetSerializer);
}
else
{
return this.Serializer == null ? this.PropertiesSerializer : new CosmosJsonSerializerWrapper(this.Serializer);
}
}

internal CosmosClientOptions Clone()
{
CosmosClientOptions cloneConfiguration = (CosmosClientOptions)this.MemberwiseClone();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ static class CosmosElementSerializer
internal static CosmosArray ToCosmosElements(
MemoryStream memoryStream,
ResourceType resourceType,
CosmosSerializationOptions cosmosSerializationOptions = null)
CosmosSerializationFormatOptions cosmosSerializationOptions = null)
{
if (!memoryStream.CanRead)
{
Expand Down Expand Up @@ -103,7 +103,7 @@ internal static Stream ToStream(
string containerRid,
IEnumerable<CosmosElement> cosmosElements,
ResourceType resourceType,
CosmosSerializationOptions cosmosSerializationOptions = null)
CosmosSerializationFormatOptions cosmosSerializationOptions = null)
{
IJsonWriter jsonWriter;
if (cosmosSerializationOptions != null)
Expand Down Expand Up @@ -172,7 +172,7 @@ internal static IEnumerable<T> Deserialize<T>(
IEnumerable<CosmosElement> cosmosElements,
ResourceType resourceType,
CosmosSerializer jsonSerializer,
CosmosSerializationOptions cosmosSerializationOptions = null)
CosmosSerializationFormatOptions cosmosSerializationOptions = null)
{
if (!cosmosElements.Any())
{
Expand Down
Binary file modified Microsoft.Azure.Cosmos/src/FeedOptions.cs
Binary file not shown.
16 changes: 14 additions & 2 deletions Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -255,15 +255,27 @@ public CosmosClientBuilder WithThrottlingRetryOptions(TimeSpan maxRetryWaitTimeO
return this;
}

/// <summary>
/// Set a custom serializer option.
/// </summary>
/// <param name="cosmosSerializerOptions">The custom class that implements <see cref="CosmosSerializer"/> </param>
/// <returns>The <see cref="CosmosClientBuilder"/> object</returns>
/// <seealso cref="CosmosSerializer"/>
/// <seealso cref="CosmosClientOptions.SerializerOptions"/>
public CosmosClientBuilder WithSerializerOptions(CosmosSerializationOptions cosmosSerializerOptions)
{
this.clientOptions.SerializerOptions = cosmosSerializerOptions;
return this;
}

/// <summary>
/// Set a custom JSON serializer.
/// </summary>
/// <param name="cosmosJsonSerializer">The custom class that implements <see cref="CosmosSerializer"/> </param>
/// <returns>The <see cref="CosmosClientBuilder"/> object</returns>
/// <seealso cref="CosmosSerializer"/>
/// <seealso cref="CosmosClientOptions.Serializer"/>
public CosmosClientBuilder WithCustomSerializer(
CosmosSerializer cosmosJsonSerializer)
public CosmosClientBuilder WithCustomSerializer(CosmosSerializer cosmosJsonSerializer)
{
this.clientOptions.Serializer = cosmosJsonSerializer;
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ public ConsistencyLevel? ConsistencyLevel
/// </remarks>
internal string SessionToken { get; set; }

internal CosmosSerializationOptions CosmosSerializationOptions { get; set; }
internal CosmosSerializationFormatOptions CosmosSerializationOptions { get; set; }

/// <summary>
/// Gets or sets the flag that enables skip take across partitions.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ private QueryResponse(
/// </remarks>
internal ClientSideRequestStatistics RequestStatistics { get; }

internal virtual CosmosSerializationOptions CosmosSerializationOptions { get; set; }
internal virtual CosmosSerializationFormatOptions CosmosSerializationOptions { get; set; }

internal bool GetHasMoreResults()
{
Expand Down Expand Up @@ -137,15 +137,15 @@ internal class QueryResponse<T> : FeedResponse<T>
{
private readonly IEnumerable<CosmosElement> cosmosElements;
private readonly CosmosSerializer jsonSerializer;
private readonly CosmosSerializationOptions serializationOptions;
private readonly CosmosSerializationFormatOptions serializationOptions;
private IEnumerable<T> resources;

private QueryResponse(
HttpStatusCode httpStatusCode,
IEnumerable<CosmosElement> cosmosElements,
CosmosQueryResponseMessageHeaders responseMessageHeaders,
CosmosSerializer jsonSerializer,
CosmosSerializationOptions serializationOptions)
CosmosSerializationFormatOptions serializationOptions)
{
this.cosmosElements = cosmosElements;
this.QueryHeaders = responseMessageHeaders;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,61 @@ namespace Microsoft.Azure.Cosmos
using System.IO;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

/// <summary>
/// The default Cosmos JSON.NET serializer
/// The default Cosmos JSON.NET serializer.
/// </summary>
public sealed class CosmosJsonDotNetSerializer : CosmosSerializer
internal sealed class CosmosJsonDotNetSerializer : CosmosSerializer
{
private static readonly Encoding DefaultEncoding = new UTF8Encoding(false, true);
private readonly JsonSerializer Serializer;

/// <summary>
/// Create a serializer that uses the JSON.net serializer
/// </summary>
/// <param name="jsonSerializerSettings">Optional serializer settings</param>
public CosmosJsonDotNetSerializer(JsonSerializerSettings jsonSerializerSettings = null)
/// <remarks>
/// This is internal to reduce exposure of JSON.net types so
/// it is easier to convert to System.Text.Json
/// </remarks>
internal CosmosJsonDotNetSerializer()
{
if (jsonSerializerSettings == null)
this.Serializer = JsonSerializer.Create();
}

/// <summary>
/// Create a serializer that uses the JSON.net serializer
/// </summary>
/// <remarks>
/// This is internal to reduce exposure of JSON.net types so
/// it is easier to convert to System.Text.Json
/// </remarks>
internal CosmosJsonDotNetSerializer(CosmosSerializationOptions cosmosSerializerOptions)
{
JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings()
{
jsonSerializerSettings = new JsonSerializerSettings()
{
NullValueHandling = NullValueHandling.Include
};
}
NullValueHandling = cosmosSerializerOptions.IgnoreNullValues ? NullValueHandling.Ignore : NullValueHandling.Include,
Formatting = cosmosSerializerOptions.Indented ? Formatting.Indented : Formatting.None,
ContractResolver = cosmosSerializerOptions.PropertyNamingPolicy == CosmosPropertyNamingPolicy.CamelCase
? new CamelCasePropertyNamesContractResolver()
: null
};

this.Serializer = JsonSerializer.Create(jsonSerializerSettings);
}

/// <summary>
/// Create a serializer that uses the JSON.net serializer
/// </summary>
/// <remarks>
/// This is internal to reduce exposure of JSON.net types so
/// it is easier to convert to System.Text.Json
/// </remarks>
internal CosmosJsonDotNetSerializer(JsonSerializerSettings jsonSerializerSettings)
{
this.Serializer = JsonSerializer.Create(jsonSerializerSettings);
}

/// <summary>
/// Convert a Stream to the passed in type.
/// </summary>
Expand All @@ -45,7 +74,7 @@ public override T FromStream<T>(Stream stream)
{
if (typeof(Stream).IsAssignableFrom(typeof(T)))
{
return (T)(object)(stream);
return (T)(object)stream;
}

using (StreamReader sr = new StreamReader(stream))
Expand Down
Loading