.NET 6自定义CosmosDB System.Text.Json序列化器丢失Id字段问题
CosmosDB使用System.Text.Json序列化时Id字段丢失问题分析
问题场景与代码示例
类层级定义
public interface IWithKey { public Guid Id {get; } } public interface IType : IWithKey { public string Payload { get; } // 其他大量复杂字段(字符串、Guid、嵌套对象等) } public class MyBaseType : IWithKey { [JsonInclude] public Guid Id {get; protected set; } public MyBaseType(Guid id) { Id = id; } } public class MyType : MyBaseType, IType { public Guid Id { get; private set; } public string Payload { get; private set; } // 其他大量复杂字段(字符串、Guid、嵌套对象等) public MyType(Guid id, string payload, /* 其他所有字段... */) : base(id) { Payload = payload; // 初始化其他字段... } }
自定义Cosmos序列化器
using System.Text.Json; using Microsoft.Azure.Cosmos; public class CosmosNetSerializer : CosmosSerializer { private readonly JsonSerializerOptions? _serializerOptions; public CosmosNetSerializer() => _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, _serializerOptions).GetAwaiter().GetResult(); } } public override Stream ToStream<T>(T input) { var outputStream = new MemoryStream(); // 断点显示input包含所有字段,包括Id JsonSerializer.SerializeAsync<T>(outputStream, input, _serializerOptions).GetAwaiter().GetResult(); // 断点显示outputStream中所有字段都已序列化,除了Id或id outputStream.Position = 0; return outputStream; } }
序列化器初始化与数据插入
var options = new(JsonSerializerDefaults.Web); var cosmosOptions = new CosmosClientOptions { ConnectionMode = //..., Serializer = new CosmosNetSerializer(options) }; var cosmosClient = CosmosClient .CreateAndInitializeAsync(connectionString, containersList, cosmosOptions); // 插入数据触发序列化 var container = cosmosClient.GetContainer("myDatabase", "myContainer"); var item = GetItem(); await container.CreateItemAsync(item, "partitionKey", null); // 返回IType类型的实例 public IType GetItem() { return new MyType(id : Guid.NewGuid(), payload: "YYYY", /* 其他所有字段... */) }
问题描述
传入序列化器的input是IType类型的MyType实例,断点显示实例包含有效值的Id字段及其他所有字段,但序列化后的outputStream中唯独Id字段丢失。
原因分析
Id字段的重复定义与隐藏关系
MyType中重新定义了public Guid Id { get; private set; },这并非对基类MyBaseType的Id字段的重写,而是**隐藏(hiding)**了基类成员。同时MyType没有显式实现IWithKey接口的Id属性,导致接口定义的Id与类中的Id字段没有明确的映射关系。System.Text.Json序列化接口类型的行为
当泛型参数T为IType时,JsonSerializer会基于接口的成员元数据进行序列化。此时序列化器只会处理IType(包括继承的IWithKey)中定义的成员,但MyType中隐藏的Id字段并没有和接口的Id属性关联起来,序列化器无法识别到该字段对应接口的Id成员。[JsonInclude]特性的作用范围限制
基类MyBaseType的Id字段标记了[JsonInclude],但该特性仅对基类自身的成员生效。当序列化的是接口类型时,序列化器不会遍历基类的成员,只会处理接口定义的成员;而MyType自身的Id字段没有标记[JsonInclude],且setter为私有,默认情况下System.Text.Json不会序列化私有setter的属性。
修复方案建议
- 移除重复的Id定义:删除MyType中自行定义的
Id字段,直接使用基类MyBaseType的Id字段,因为MyBaseType已经实现了IWithKey接口,MyType无需重复定义。 - 显式关联接口成员:如果必须保留MyType的Id字段,需显式实现IWithKey的Id属性,或者给MyType的Id字段添加
[JsonInclude]特性。 - 基于实际类型序列化:在序列化器的
ToStream方法中,使用input.GetType()作为序列化类型,而非泛型参数T,即修改序列化代码为:JsonSerializer.SerializeAsync(outputStream, input, input.GetType(), _serializerOptions),让序列化器基于实际的MyType类型处理所有成员。
内容的提问来源于stack exchange,提问作者jeancallisti
相关产品推荐
相关产品推荐

