Azure CosmosDb logo

System.Text.Json.JsonPropertyName not working for CosmosDb in .NET Core/5/6?

This post was most recently updated on August 31st, 2022.

3 min read.

This article explains how to fix an annoying issue with Microsoft’s SDK for CosmosDb v3 – it comes with a Newtonsoft.Json dependency, that most of Microsoft’s recent packages have let go of. With .NET Core 3.1 having shipped with System.Text.Json included, and (mostly) replacing Newtonsoft.Json, it’s kind of the preferred option.

However, CosmosDb v3 SDK doesn’t support it and by default requires you to use Newtonsoft.Json to override the property names, if you want to – for example – map entities with PascalCase naming with data in CosmosDb that’s using camelCase.

This is described on GitHub for CosmosDb SDK as such:

.NET v3 SDK was released before System.Text.Json was GA. Switching the entire SDK over from newtonsoft to System.Text.Json is a breaking change which would require a v4 SDK.

https://github.com/Azure/azure-cosmos-dotnet-v3/issues/2533

So until v4, Newtonsoft it is. Or is it?

Problem

Let’s take a quick step back again and see what the issue was. When you create a model somewhat like this:

My entity configuration with property names in PascalCase - this doesn't match what's in the database.
My entity configuration with property names in PascalCase – doesn’t match what’s in the database.

You’ll get thrown NullReferenceExceptions or something similar because your DeviceId and UserId are null – even though there’s data in the database!

Oh, but CosmosDb uses camelCase instead of PascalCase, so obviously, these properties won’t get mapped to anything. To fix this, you add the JsonPropertyName-attribute to map the data in your CosmosDb containers to your C# model:

Overriding the property name in the database using JsonPropertyName -attribute for your entities.
Overriding the property name in the database using JsonPropertyName -attribute for your entities.

But the problem persists – it’s almost as if the JsonPropertyName -attribute isn’t applied at all!

And similarly, renaming your properties to camelCase fixes the issue (but feels wrong).

You could also add a reference to Newtonsoft.Json – but if you don’t want to do that, there are other things you can do.

Solution

CosmosDb SDK v3 needs a quick additional step to make it understand the “new” System.Text.Json.JsonPropertyName. Let’s take a look!

Solution / Workaround

Time needed: 30 minutes

How to configure JSON casing for CosmosDb (v3)?

  1. Add the JsonPropertyName attributes

    You can match pretty much any property in your C# class to whatever property in the container – but traditionally it does look somewhat like this:

    [JsonPropertyName("deviceId")]
    public string DeviceId{ get; set; }


    And probably you need to add this as well:
    using System.Text.Json.Serialization;

    Or, as Gavin helpfully points out in his comment below, if the casing issue applies to all of your properties in all of your classes, you can simply override the default property naming with JsonSerializerOptions like this (compare to step 3 to see how to use it in your code):
    JsonSerializerOptions jsonSerializerOptions = new()
    {
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    };

  2. Create a new class for serialization

    Now you’ll need a custom serializer class. This class takes care of the actual serialization, as hinted before, CosmosDb (v3 SDK) doesn’t know how to do it with System.Text.Json. The whole sample is shown in appendix 1.

  3. Configure your CosmosClient with this “new” serializer

    Wherever you instantiate or configure your CosmosClient, pass this CosmosClientOptions object to it and you should be good!

    This would look something like this:
    // Configure JsonSerializerOptions
    var opt = new JsonSerializerOptions()
    {
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
    };
    // Configure CosmosSystemTextJsonSerializer
    var serializer
    = new CosmosSystemTextJsonSerializer(opt);
    // Configure CosmosClientOptions
    var clientOptions = new CosmosClientOptions()
    {
    Serializer = serializer
    };

    And finally:
    var client
    = new Microsoft.Azure.Cosmos.CosmosClient(account, key, clientOptions);

  4. And voilà! You should be good 😎

    Now your mapping should work just fine!

References and appendices

Appendix 1

namespace Contoso.Shared
{
    using System.IO;
    using System.Text.Json;
    using Azure.Core.Serialization;
    using Microsoft.Azure.Cosmos;

    public class CosmosSystemTextJsonSerializer : CosmosSerializer
    {
        private readonly JsonObjectSerializer systemTextJsonSerializer;

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

        public override T FromStream<T>(Stream 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, typeof(T), default);
            streamPayload.Position = 0;
            return streamPayload;
        }
    }
}
mm
4 2 votes
Article Rating
Subscribe
Notify of
guest

4 Comments
most voted
newest oldest
Inline Feedbacks
View all comments