CosmosClient : Custom json converter works out of the box with NewtonSoft, not with System.Text.Json

huangapple go评论83阅读模式
英文:

CosmosClient : Custom json converter works out of the box with NewtonSoft, not with System.Text.Json

问题

I understand you want a translation of the provided text, excluding the code. Here's the translation of the non-code parts:

Scenario:

  • 我有一个存储在Azure中的CosmosDb容器。
  • 我有这样的数据类,我可以读取和写入它:
public class MyClass {
    public Guid Id { get; private set; }
    public string PartitionKey { get; private set; }
   
    [JsonConverter(typeof(MyTypeJsonConverter))]
    public MyType Custom { get; private set; }

    [JsonConstructor] 
    public MyClass(Guid id, string pk, MyType custom) {
         Custom = custom; Id = id; PartitionKey = pk;
    }
}
  • 如您所见,有一个自定义类型MyType,它使用自定义转换器进行转换。

  • 出于测试目的,我使用Newtonsoft和System.Text编写了转换器:

public class MyTypeJsonConverter : JsonConverter<MyType> {
    public override bool CanConvert(Type objectType) { ... }
    public override void Write(...) { ... } 
    public override MyType Read(...) { ... } 
}
  
public class ContextNewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter {
    ... 
}
  • 我知道MyType和转换器都可以正常工作,因为当我只是这样做时,序列化和反序列化与System.Text.Json和Newtonsoft.Json的预期工作都正常:
// Newtonsoft
// var myClass = JsonConvert.DeserializeObject<MyClass>(someJson);

// System.Text.Json
var myClass2 = JsonSerializer.Deserialize<MyClass>(someJson,
   new JsonSerializerOptions()
      { IgnoreNullValues = false, PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
  • 当我从CosmosDb中读取和写入对象时,也会发生类似的反序列化。
CosmosClientOptions options = new()
{
    ConnectionMode = DebugHelper.DebugMode ? 
        ConnectionMode.Gateway : ConnectionMode.Direct,
    SerializerOptions = new CosmosSerializationOptions
    {
        IgnoreNullValues = false,
        PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase
    },
};
var cosmosClient = CosmosClient
    .CreateAndInitializeAsync(connectionString, 
          containersList, options)
    .GetAwaiter()
    .GetResult();

var container = cosmosClient.GetContainer("mydatabase", "mycontainer");

var items = container.GetItemLinqQueryable<MyType>(allowSynchronousQueryExecution: true);
foreach (var item in items)
{
    await container.DeleteItemAsync<MyType>(item.Id.ToString(), new PartitionKey(item.PartitionKey));
}

Problem:
上面的代码与NewtonSoft版本完美运行...
...但在System.Text.Json版本中失败。

---- Newtonsoft.Json.JsonSerializationException: 将值“my string value”转换为类型“MyType”时出错。
-------- System.ArgumentException: 无法将System.String转换为MyType。

这个异常不发生在转换器的Read和Write函数内部。它发生在"之前"。就像[JsonConverter(...)]属性被JsonSerializer.Deserialize<Mytype>理解,但不被cosmosContainer.DoSomethingWithItem<MyType>理解。

Again; 在普通序列化/反序列化中,它可以工作,当我使用Newtonsoft时,与CosmosClient一起工作。

Question:

  • (在深入研究复杂的Json之前)您能否发现明显的错误?
  • 我是否需要注册自定义JsonConverter,如果是的话,最简洁的方法是什么?(除非您能证明Newtonsoft可以在不使用它的情况下执行此操作,但System.Text.Json不能)
英文:

Scenario :

  • I have a cosmosDb container stored in Azure.

  • I have this kind of data class that I read and write to it :

    public class MyClass {
         public Guid Id { get; private set; }
         public string PartitionKey { get; private set; }
    
         [JsonConverter(typeof(MyTypeJsonConverter))]
         //[Newtonsoft.Json.JsonConverter(typeof(MyTypeNewtonsoftJsonConverter))]
         public MyType Custom { get; private set; }
    
         [JsonConstructor] // &lt;-- I&#39;ve tried with and without this
         public MyClass(Guid id, string pk, MyType custom) {
              Custom = custom; Id = id; PartitionKey = pk;
         }
    }
    
  • as you can see there's a custom type, MyType, that gets converted with a custom converter.

  • For test purposes, I wrote the converter both with Newtonsoft and System.Text:

    public class MyTypeJsonConverter : JsonConverter&lt;MyType&gt; {
    
         public override bool CanConvert(Type objectType) { ... }
         public override void Write(...) { ... } 
         public override Context Read(...) { ... } 
    }
    
    public class ContextNewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter {
    
         ... 
    }
    
  • I know that MyType works and that the converters work because serialization and deserialization work as expected both with System.Text.Json and Newtonsoft.Json when I do just this :

      Newtonsoft
      //var myClass = JsonConvert.DeserializeObject&lt;MyClass&gt;(someJson);
    
      //System.Text.Json
      var myClass2 =
         JsonSerializer.Deserialize&lt;MyClass&gt;(someJson,
            new JsonSerializerOptions()
               { IgnoreNullValues = false, PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
    
  • A similar deserialization happens too when I read and write objects from CosmosDb.

         CosmosClientOptions options = new()
         {
               ConnectionMode = DebugHelper.DebugMode ? 
                     ConnectionMode.Gateway : ConnectionMode.Direct,
               SerializerOptions = new CosmosSerializationOptions
               {
               IgnoreNullValues = false,
               PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase
               },
         };
         var cosmosClient = CosmosClient
                     .CreateAndInitializeAsync(connectionString, 
                           containersList, options)
                     .GetAwaiter()
                     .GetResult();
    
         var container = cosmosClient .GetContainer(&quot;mydatabase&quot;, &quot;mycontainer&quot;);
    
    
         var items = container.GetItemLinqQueryable&lt;MyType&gt;(allowSynchronousQueryExecution: true);
         foreach (var item in items)
         {
               await container.DeleteItemAsync&lt;MyType&gt;(item.Id.ToString(), new PartitionKey(item.PartitionKey));
         }
    

Problem :

the code just above works perfectly with the NewtonSoft version...
...But fails with the System.Text.Json version.

> ---- Newtonsoft.Json.JsonSerializationException : Error converting value "my string value" to type
> 'MyType'.
> -------- System.ArgumentException : Could not cast or convert from System.String to MyType.

That exception does NOT happen inside the Read and Write functons of the converter. It happens "beforehand". It's like the [JsonConverter(...)] attribute is understood by JsonSerializer.Deserialize&lt;Mytype&gt; but not by cosmosContainer.DoSomethingWithItem&lt;MyType&gt; .

Again; it works with a plain serialization/desrialization, and it works with the CosmosClient when I use Newtonsoft.

Question :

  • (before you dive into complex Json) Can you spot an obvious mistake?
  • Do I need to register the custom JsonConverter, and if yes what's the most compact way of doing that? (Anything that doesn't require me to implement an entire custom Serializer to pass to CosmosDb... unless you can prove to me that there's a good explanation why Newtonsoft can do it without it but System.Text.Json can't)

答案1

得分: 2

Yes, you can (and should, in your case) register a custom Serializer:

使用自定义的序列化程序,您可以(也应该在您的情况下)进行注册:

英文:

Yes, you can (and should, in your case) register a custom Serializer:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos;

namespace FooBar
{
    // Custom implementation of CosmosSerializer which works with System.Text.Json instead of Json.NET.
    // https://github.com/Azure/azure-cosmos-dotnet-v3/issues/202
    // This is temporary, until CosmosDB SDK v4 is available, which should remove the Json.NET dependency.
    public class CosmosNetSerializer : CosmosSerializer
    {
        private readonly JsonSerializerOptions _serializerOptions;

        public CosmosNetSerializer() =&gt; this._serializerOptions = null;

        public CosmosNetSerializer(JsonSerializerOptions serializerOptions) =&gt; this._serializerOptions = serializerOptions;

        public override T FromStream&lt;T&gt;(Stream stream)
        {
            using (stream)
            {
                if (typeof(Stream).IsAssignableFrom(typeof(T)))
                {
                    return (T)(object)stream;
                }

                return JsonSerializer.DeserializeAsync&lt;T&gt;(stream, this._serializerOptions).GetAwaiter().GetResult();
            }
        }

        public override Stream ToStream&lt;T&gt;(T input)
        {
            var outputStream = new MemoryStream();

            //TODO: replace with sync variant too?
            JsonSerializer.SerializeAsync&lt;T&gt;(outputStream, input, this._serializerOptions).GetAwaiter().GetResult();

            outputStream.Position = 0;
            return outputStream;
        }
    }
}
CosmosClientBuilder clientBuilder = new CosmosClientBuilder(sysConfig.CosmosEndpointUri, tokenCredential)
  .WithConnectionModeDirect()
// ... With()...
  .WithCustomSerializer(new CosmosNetSerializer(Globals.JsonSerializerOptions))

Full example here

huangapple
  • 本文由 发表于 2023年5月10日 21:41:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/76219161.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定