Skip to content

Commit

Permalink
Samples: Adds STJ LINQ Serializer Example (#4420)
Browse files Browse the repository at this point in the history
* initial commit

* bump sample version

* update comments

* sln file fix

* update comment

* Update Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializer.cs

Co-authored-by: Matias Quaranta <ealsur@users.noreply.github.com>

* remarks fix

* xml fix

---------

Co-authored-by: Matias Quaranta <ealsur@users.noreply.github.com>
  • Loading branch information
Maya-Painter and ealsur authored Apr 18, 2024
1 parent a76666d commit 6131998
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 114 deletions.
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
namespace Cosmos.Samples.Shared
{
using System.IO;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using Azure.Core.Serialization;
using Microsoft.Azure.Cosmos;

/// <summary>
/// Uses <see cref="Azure.Core.Serialization.JsonObjectSerializer"/> which leverages System.Text.Json providing a simple API to interact with on the Azure SDKs.
/// </summary>
/// <remarks>
/// For item CRUD operations and non-LINQ queries, implementing CosmosSerializer is sufficient. To support LINQ query translations as well, CosmosLinqSerializer must be implemented.
/// </remarks>
// <SystemTextJsonSerializer>
public class CosmosSystemTextJsonSerializer : CosmosSerializer
public class CosmosSystemTextJsonSerializer : CosmosLinqSerializer
{
private readonly JsonObjectSerializer systemTextJsonSerializer;
private readonly JsonSerializerOptions jsonSerializerOptions;

public CosmosSystemTextJsonSerializer(JsonSerializerOptions jsonSerializerOptions)
{
this.systemTextJsonSerializer = new JsonObjectSerializer(jsonSerializerOptions);
this.jsonSerializerOptions = jsonSerializerOptions;
}

public override T FromStream<T>(Stream stream)
Expand Down Expand Up @@ -44,6 +51,30 @@ public override Stream ToStream<T>(T input)
streamPayload.Position = 0;
return streamPayload;
}

public override string SerializeMemberName(MemberInfo memberInfo)
{
JsonExtensionDataAttribute jsonExtensionDataAttribute = memberInfo.GetCustomAttribute<JsonExtensionDataAttribute>(true);
if (jsonExtensionDataAttribute != null)
{
return null;
}

JsonPropertyNameAttribute jsonPropertyNameAttribute = memberInfo.GetCustomAttribute<JsonPropertyNameAttribute>(true);
if (!string.IsNullOrEmpty(jsonPropertyNameAttribute?.Name))
{
return jsonPropertyNameAttribute.Name;
}

if (this.jsonSerializerOptions.PropertyNamingPolicy != null)
{
return this.jsonSerializerOptions.PropertyNamingPolicy.ConvertName(memberInfo.Name);
}

// Do any additional handling of JsonSerializerOptions here.

return memberInfo.Name;
}
}
// </SystemTextJsonSerializer>
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,6 @@ private static async Task RunDemo()
Console.WriteLine($"Created Completed activity with id {createCompletedActivity.Resource.Id} that cost {createCompletedActivity.RequestCharge}");

// Execute queries materializing responses using System.Text.Json
// NOTE: GetItemLinqQueryable does not support System.Text.Json attributes. LINQ will not translate the name based on the attributes
// which can result in no or invalid results coming back. https://github.com/Azure/azure-cosmos-dotnet-v3/issues/3250
using FeedIterator<ToDoActivity> iterator = container.GetItemQueryIterator<ToDoActivity>("select * from c where c.status = 'Completed'");
while (iterator.HasMoreResults)
{
Expand Down Expand Up @@ -177,19 +175,15 @@ private static async Task CleanupAsync()
// <Model>
public class ToDoActivity
{
// Note: System.Text.Json attributes such as JsonPropertyName are currently applied on item CRUD operations and non-LINQ queries, but not on LINQ queries
[JsonPropertyName("id")]
public string Id { get; set; }

// Note: System.Text.Json attributes such as JsonPropertyName are currently applied on item CRUD operations and non-LINQ queries, but not on LINQ queries
[JsonPropertyName("partitionKey")]
public string PartitionKey { get; set; }

// Note: System.Text.Json attributes such as JsonPropertyName are currently applied on item CRUD operations and non-LINQ queries, but not on LINQ queries
[JsonPropertyName("activityId")]
public string ActivityId { get; set; }

// Note: System.Text.Json attributes such as JsonPropertyName are currently applied on item CRUD operations and non-LINQ queries, but not on LINQ queries
[JsonPropertyName("status")]
public string Status { get; set; }
}
Expand Down
109 changes: 23 additions & 86 deletions Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializer.cs
Original file line number Diff line number Diff line change
@@ -1,88 +1,25 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos
{
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos
{
using System.Reflection;

/// <summary>
/// This abstract class can be implemented to allow a custom serializer (Non [Json.NET serializer](https://www.newtonsoft.com/json/help/html/Introduction.htm)'s)
/// to be used by the CosmosClient for LINQ queries.
/// </summary>
/// <example>
/// This example implements the CosmosLinqSerializer contract.
/// This example custom serializer will honor System.Text.Json attributes.
/// <code language="c#">
/// <![CDATA[
/// class SystemTextJsonSerializer : CosmosLinqSerializer
/// {
/// private readonly JsonObjectSerializer systemTextJsonSerializer;
///
/// public SystemTextJsonSerializer(JsonSerializerOptions jsonSerializerOptions)
/// {
/// this.systemTextJsonSerializer = new JsonObjectSerializer(jsonSerializerOptions);
/// }
///
/// public override T FromStream<T>(Stream stream)
/// {
/// if (stream == null)
/// throw new ArgumentNullException(nameof(stream));
///
/// using (stream)
/// {
/// if (stream.CanSeek && stream.Length == 0)
/// {
/// return default;
/// }
///
/// if (typeof(Stream).IsAssignableFrom(typeof(T)))
/// {
/// return (T)(object)stream;
/// }
///
/// return (T)this.systemTextJsonSerializer.Deserialize(stream, typeof(T), default);
/// }
/// }
///
/// public override Stream ToStream<T>(T input)
/// {
/// MemoryStream streamPayload = new MemoryStream();
/// this.systemTextJsonSerializer.Serialize(streamPayload, input, input.GetType(), default);
/// streamPayload.Position = 0;
/// return streamPayload;
/// }
///
/// public override string SerializeMemberName(MemberInfo memberInfo)
/// {
/// System.Text.Json.Serialization.JsonExtensionDataAttribute jsonExtensionDataAttribute =
/// memberInfo.GetCustomAttribute<System.Text.Json.Serialization.JsonExtensionDataAttribute>(true);
/// if (jsonExtensionDataAttribute != null)
/// {
/// return null;
/// }
///
/// JsonPropertyNameAttribute jsonPropertyNameAttribute = memberInfo.GetCustomAttribute<JsonPropertyNameAttribute>(true);
///
/// string memberName = !string.IsNullOrEmpty(jsonPropertyNameAttribute?.Name)
/// ? jsonPropertyNameAttribute.Name
/// : memberInfo.Name;
///
/// // Users must add handling for any additional attributes here
///
/// return memberName;
/// }
/// }
/// ]]>
/// </code>
/// </example>
public abstract class CosmosLinqSerializer : CosmosSerializer
{
/// <summary>
/// Convert a MemberInfo to a string for use in LINQ query translation.
/// This must be implemented when using a custom serializer for LINQ queries.
/// </summary>
/// <param name="memberInfo">Any MemberInfo used in the query.</param>
/// <returns>A serialized representation of the member.</returns>
public abstract string SerializeMemberName(MemberInfo memberInfo);
}
}
/// <summary>
/// This abstract class can be implemented to allow a custom serializer (Non [Json.NET serializer](https://www.newtonsoft.com/json/help/html/Introduction.htm)'s)
/// to be used by the CosmosClient for LINQ queries.
/// </summary>
/// <remarks>
/// Refer to the <see href="https://github.com/Azure/azure-cosmos-dotnet-v3/blob/master/Microsoft.Azure.Cosmos.Samples/Usage/SystemTextJson/CosmosSystemTextJsonSerializer.cs">sample project</see> for a full implementation.
/// </remarks>
public abstract class CosmosLinqSerializer : CosmosSerializer
{
/// <summary>
/// Convert a MemberInfo to a string for use in LINQ query translation.
/// This must be implemented when using a custom serializer for LINQ queries.
/// </summary>
/// <param name="memberInfo">Any MemberInfo used in the query.</param>
/// <returns>A serialized representation of the member.</returns>
public abstract string SerializeMemberName(MemberInfo memberInfo);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -836,11 +836,13 @@ public override void SerializeAsXml(XmlWriter xmlWriter)

class SystemTextJsonLinqSerializer : CosmosLinqSerializer
{
private readonly JsonObjectSerializer systemTextJsonSerializer;
private readonly JsonObjectSerializer systemTextJsonSerializer;
private readonly JsonSerializerOptions jsonSerializerOptions;

public SystemTextJsonLinqSerializer(JsonSerializerOptions jsonSerializerOptions)
{
this.systemTextJsonSerializer = new JsonObjectSerializer(jsonSerializerOptions);
this.systemTextJsonSerializer = new JsonObjectSerializer(jsonSerializerOptions);
this.jsonSerializerOptions = jsonSerializerOptions;
}

public override T FromStream<T>(Stream stream)
Expand Down Expand Up @@ -882,12 +884,19 @@ public override string SerializeMemberName(MemberInfo memberInfo)
}

JsonPropertyNameAttribute jsonPropertyNameAttribute = memberInfo.GetCustomAttribute<JsonPropertyNameAttribute>(true);

string memberName = !string.IsNullOrEmpty(jsonPropertyNameAttribute?.Name)
? jsonPropertyNameAttribute.Name
: memberInfo.Name;

return memberName;
if (!string.IsNullOrEmpty(jsonPropertyNameAttribute?.Name))
{
return jsonPropertyNameAttribute.Name;
}

if (this.jsonSerializerOptions.PropertyNamingPolicy != null)
{
return this.jsonSerializerOptions.PropertyNamingPolicy.ConvertName(memberInfo.Name);
}

// Do any additional handling of JsonSerializerOptions here.

return memberInfo.Name;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ class TestCustomJsonLinqSerializer : CosmosLinqSerializer
{
private readonly JsonObjectSerializer systemTextJsonSerializer;

public static readonly System.Text.Json.JsonSerializerOptions JsonOptions = new()
private readonly System.Text.Json.JsonSerializerOptions jsonSerializerOptions = new()
{
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,
PropertyNameCaseInsensitive = true,
Expand All @@ -209,7 +209,7 @@ class TestCustomJsonLinqSerializer : CosmosLinqSerializer

public TestCustomJsonLinqSerializer()
{
this.systemTextJsonSerializer = new JsonObjectSerializer(JsonOptions);
this.systemTextJsonSerializer = new JsonObjectSerializer(this.jsonSerializerOptions);
}

public override T FromStream<T>(Stream stream)
Expand Down Expand Up @@ -237,24 +237,31 @@ public override Stream ToStream<T>(T input)
this.systemTextJsonSerializer.Serialize(stream, input, input.GetType(), default);
stream.Position = 0;
return stream;
}

public override string SerializeMemberName(MemberInfo memberInfo)
}

public override string SerializeMemberName(MemberInfo memberInfo)
{
System.Text.Json.Serialization.JsonExtensionDataAttribute jsonExtensionDataAttribute =
memberInfo.GetCustomAttribute<System.Text.Json.Serialization.JsonExtensionDataAttribute>(true);
if (jsonExtensionDataAttribute != null)
{
return null;
}

JsonPropertyNameAttribute jsonPropertyNameAttribute = memberInfo.GetCustomAttribute<JsonPropertyNameAttribute>(true);

string memberName = jsonPropertyNameAttribute != null && !string.IsNullOrEmpty(jsonPropertyNameAttribute.Name)
? jsonPropertyNameAttribute.Name
: memberInfo.Name;

return memberName;

JsonPropertyNameAttribute jsonPropertyNameAttribute = memberInfo.GetCustomAttribute<JsonPropertyNameAttribute>(true);
if (!string.IsNullOrEmpty(jsonPropertyNameAttribute?.Name))
{
return jsonPropertyNameAttribute.Name;
}

if (this.jsonSerializerOptions.PropertyNamingPolicy != null)
{
return this.jsonSerializerOptions.PropertyNamingPolicy.ConvertName(memberInfo.Name);
}

// Do any additional handling of JsonSerializerOptions here.

return memberInfo.Name;
}
}

Expand Down

0 comments on commit 6131998

Please sign in to comment.