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

LINQ: Add support for System.Text.Json attributes #3250

Closed
j82w opened this issue Jun 3, 2022 · 6 comments · Fixed by #4138
Closed

LINQ: Add support for System.Text.Json attributes #3250

j82w opened this issue Jun 3, 2022 · 6 comments · Fixed by #4138
Labels
feature-request New feature or request LINQ QUERY

Comments

@j82w
Copy link
Contributor

j82w commented Jun 3, 2022

If you follow this sample and use the System.Text.Json as the default serializer LINQ will not translate the attribute so the incorrect query will be generated. This will cause the query to return no or incorrect results.

Follow the sample listed here: https://github.com/Azure/azure-cosmos-dotnet-v3/blob/master/Microsoft.Azure.Cosmos.Samples/Usage/SystemTextJson/CosmosSystemTextJsonSerializer.cs

Update the class like the sample:

[JsonPropertyName("poolId")] // after change
//[JsonProperty("poolId")] // before change
public string PoolId { get; set; }

Try to do a LINQ query:

 container.GetItemLinqQueryable<T>().Where(x => x.PoolId != null).ToFeedIterator();

The query will execute but it will return no results because it will be translated as 'PoolId' instead of the expected 'poolId'.

It seems the following code needs to be updated to handle the new System.Text.Json types:

JsonPropertyAttribute jsonPropertyAttribute = memberInfo.GetCustomAttribute<JsonPropertyAttribute>(true);

@j82w j82w added feature-request New feature or request LINQ labels Jun 3, 2022
@vmachacek
Copy link

You can supply linqSerializerOptions param and set its PropertyNamingPolicy to CamelCase. But since you set the custom serializer you cannot do it globally unfortunately, you would have to supply it with each invocation (that works for me).

@YipingRuan
Copy link

With linqSerializerOptions, projection in LinQ with upper case seems not working for me:
.Select(x => new { A = 55, b = 1.5, id = x.Id })

Note the value of A and id

PropertyNamingPolicy = CamelCase
image

default
image

@Maya-Painter
Copy link
Contributor

Hi all, we've merged what we hope will be a solution to this issue by allowing the custom serializer to be used for LINQ translations. This also unblocks having System.Text.Json attributes applied in LINQ queries.

We plan to release this as part of the next preview package, but I also wanted to take this time to confirm these changes meet your needs before finalizing them as part of our public API. If you try the changes out, please follow up in the thread if this update addresses your concerns. If it doesn't, please let us know why.

To use your own custom serializer for LINQ translations you must implement CosmosLinqSerializer and set it as a custom serializer on the CosmosClient. There is a sample of this for serialization with System.Text.Json in CosmosLinqSerializer.cs.

@machie27
Copy link

machie27 commented Jan 11, 2024

@Maya-Painter, that is good news!

When is this preview package available on NuGet?

If we register a custom serializer in CosmosClientOptions will this also be implemented by the CosmosLinqSerializer?

For example if we do it like this:

Custom serializator class:

    public sealed class CosmosSystemTextJsonSerializer : CosmosSerializer, CosmosLinqSerializer
    {
        private readonly JsonObjectSerializer _systemTextJsonSerializer;

        public CosmosSystemTextJsonSerializer(JsonSerializerOptions jsonSerializerOptions)
        {
            _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)_systemTextJsonSerializer.Deserialize(stream, typeof(T), default);
            }
        }

        public override Stream ToStream<T>(T input)
        {
            var streamPayload = new MemoryStream();
            _systemTextJsonSerializer.Serialize(streamPayload, input, typeof(T), default);
            streamPayload.Position = 0;
            return streamPayload;
        }
    }

and then register it like so for example:

CosmosClientOptions cosmosClientOptions = new()
{
    ...
    Serializer = new CosmosSystemTextJsonSerializer(new JsonSerializerOptions()
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
        Converters =
        {
            new JsonStringEnumConverter()
        }
    })
};

@v-mghabin
Copy link

v-mghabin commented Apr 3, 2024

@machie27 CosmosLinqSerializer is inaccessible due to its protection level, its internal class and can't be inherited.
Can u pls mention if there is any solution to this?

@machie27
Copy link

machie27 commented Apr 4, 2024

Hi @v-mghabin,

Sorry I've mentioned this in another request as well and got a response there from @Maya-Painter. This isn't the way it should be implemented indeed. Link to which I'm referring to: link

please note that in this example I also convert the membernames by setting the policy naming explicitly.
See my question here as well: link

I've an example here:

public class CosmosSystemTextJsonSerializer : CosmosLinqSerializer
{
    private readonly JsonObjectSerializer _systemTextJsonSerializer;

    public CosmosSystemTextJsonSerializer(JsonSerializerOptions jsonSerializerOptions)
    {
        _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)_systemTextJsonSerializer.Deserialize(stream, typeof(T), default);
        }
    }

    public override string SerializeMemberName(MemberInfo memberInfo)
    {
        var jsonPropertyNameAttribute = memberInfo.GetCustomAttribute<JsonPropertyNameAttribute>(true);

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

        return JsonNamingPolicy.CamelCase.ConvertName(memberName);
    }

    public override Stream ToStream<T>(T input)
    {
        var streamPayload = new MemoryStream();

        _systemTextJsonSerializer.Serialize(streamPayload, input, typeof(T), default);
        streamPayload.Position = 0;
        return streamPayload;
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature-request New feature or request LINQ QUERY
Projects
Status: Done
7 participants