#SharePointProblems | Koskila.net

Solutions are worthless unless shared! Antti K. Koskela's Personal Professional Blog
>

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

koskila
Reading Time 4 min
Word Count 674 words
Comments 4 comments
Rating 4 (2 votes)
View

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;
        }
    }
}

Comments

Interactive comments not implemented yet. Showing legacy comments migrated from WordPress.
Ezha Yoes
2021-10-24 09:58:19)
Feo
Antti K. Koskela
2021-11-01 16:33:29
Eh?
2022-06-03 21:26:50)
Thanks Antti! This was exactly the info I needed to help with our migration from full framework to dotnet core. I made one change and used
            JsonSerializerOptions jsonSerializerOptions = new()
            {
                DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
                PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
            };
That way none of the properties on my POCOs needed decorating with the JsonPropertyName attribute which is a nice timesaver
2022-06-11 22:28:06
Hi Gavin, Fancy seeing you here! 😁 That's a worthy addition to the article, I'll adjust the content accordingly. Thanks for the tip!