azure CosmosClient:自定义json转换器与NewtonSoft一起工作,而不是System.Text.Json

osh3o9ms  于 2023-05-18  发布在  其他
关注(0)|答案(1)|浏览(100)

场景:

  • 我有一个cosmosDb容器存储在Azure中。
  • 我有这样的数据类,我可以读写:
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] // <-- I've tried with and without this
     public MyClass(Guid id, string pk, MyType custom) {
          Custom = custom; Id = id; PartitionKey = pk;
     }
}
  • 正如你所看到的,有一个自定义类型MyType,它被自定义转换器转换。
  • 为了测试目的,我用Newtonsoft和System编写了转换器。文本:
public class MyTypeJsonConverter : JsonConverter<MyType> {

     public override bool CanConvert(Type objectType) { ... }
     public override void Write(...) { ... } 
     public override Context 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));
     }

问题:
上面的代码与NewtonSoft版本完美配合......但System.Text.Json版本失败。

---- Newtonsoft.Json.JsonSerializationException:将值“my string value”转换为类型“MyType”时出错。--------- System.ArgumentException:无法从System.String强制转换或转换为MyType。
该异常不会在转换器的ReadWrite函数内**发生。这是“事先”发生的。这就像[JsonConverter(...)]属性被JsonSerializer.Deserialize<Mytype>理解,而不是cosmosContainer.DoSomethingWithItem<MyType>

**同样;它与普通的序列化/反序列化一起工作,**当我使用Newtonsoft时,它与CosmosClient一起工作。
问题:

  • (在你深入到复杂的Json之前)你能发现一个明显的错误吗?
  • 我是否需要注册自定义JsonConverter,如果是的话,最简洁的方式是什么?(任何不需要我实现一个完整的自定义序列化器**传递给CosmosDb的东西...除非你能向我证明,有一个很好的解释为什么Newtonsoft可以不使用它而System.Text.Json不能)
ztyzrc3y

ztyzrc3y1#

是的,您可以(在您的情况下,也应该)注册一个自定义序列化器:

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() => this._serializerOptions = null;

        public CosmosNetSerializer(JsonSerializerOptions serializerOptions) => this._serializerOptions = serializerOptions;

        public override T FromStream<T>(Stream stream)
        {
            using (stream)
            {
                if (typeof(Stream).IsAssignableFrom(typeof(T)))
                {
                    return (T)(object)stream;
                }

                return JsonSerializer.DeserializeAsync<T>(stream, this._serializerOptions).GetAwaiter().GetResult();
            }
        }

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

            //TODO: replace with sync variant too?
            JsonSerializer.SerializeAsync<T>(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))

完整示例here

相关问题