C#中System.Text.Json如何指定接口反序列化的目标类?
问题原因分析
你遇到的核心问题是:虽然通过JsonTypeInfo.CreateObject指定了IUser的实例化方式,但System.Text.Json在处理接口类型反序列化时,默认只会基于接口的成员元数据做字段映射,并未自动关联到实现类BaseUser的属性绑定逻辑。即便你公开了属性setter,序列化器也无法将JSON字段正确赋值到BaseUser实例上——因为你的TypeResolver只处理了对象创建逻辑,没有补充属性的序列化/反序列化规则。
可行解决方案
方案一:完善TypeInfoResolver的属性映射逻辑
修改TypeResolver,在指定实例化逻辑的同时,将BaseUser的属性元数据注入到IUser的JsonTypeInfo中,让序列化器明确字段映射规则:
public static class TypeResolver { public static Action<JsonTypeInfo> GetUserTypeResolver() { return typeInfo => { if (typeInfo.Type == typeof(IUser)) { // 指定IUser的实例化方式 typeInfo.CreateObject = () => new BaseUser(); // 从BaseUser获取完整的属性元数据,替换IUser的默认元数据 var baseUserTypeInfo = JsonSerializerOptions.Default.GetTypeInfo(typeof(BaseUser)) as JsonTypeInfo; if (baseUserTypeInfo != null) { typeInfo.Properties.Clear(); foreach (var property in baseUserTypeInfo.Properties) { typeInfo.Properties.Add(property); } } } }; } }
同时,无需强制公开属性setter,用[JsonInclude]标记即可让序列化器访问init-only或私有set属性:
public class BaseUser : IUser { [JsonInclude] public string Username { get; init; } [JsonInclude] public List<string> GroupIds { get; init; } // 用[JsonConstructor]标记无参构造函数,允许序列化器访问 [JsonConstructor] private BaseUser() {} // 业务逻辑构造函数 public BaseUser(string username, List<string> groupIds) { Username = username; GroupIds = groupIds; } }
方案二:自定义JsonConverter
这是更直观可控的方案,直接实现接口类型的序列化/反序列化逻辑:
- 在客户端库中创建转换器:
public class UserConverter : JsonConverter<IUser> { public override IUser Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { // 直接将JSON反序列化为BaseUser实例 return JsonSerializer.Deserialize<BaseUser>(ref reader, options); } public override void Write(Utf8JsonWriter writer, IUser value, JsonSerializerOptions options) { // 序列化时将IUser视为BaseUser处理 JsonSerializer.Serialize(writer, (BaseUser)value, options); } }
- 在客户端的序列化配置中注册转换器:
var options = new JsonSerializerOptions { Converters = { new UserConverter() } };
该方案无需修改BaseUser的构造函数访问权限,逻辑清晰且可维护性强。
方案三:利用接口多态特性(.NET 7+)
如果共享库可以引用具体实现类,可在IUser接口上添加[JsonDerivedType]标记指定实现类型:
// 共享库中的IUser定义 [JsonDerivedType(typeof(BaseUser), typeDiscriminator: "BaseUser")] public interface IUser { string Username { get; } List<string> GroupIds { get; } }
此方案局限性在于共享库需依赖具体实现类,若你的分层设计不允许跨库引用,建议优先选择前两种方案。
额外注意事项
- 服务端序列化
LoginSucceededPacket时,需配置对应的规则(如相同的转换器或TypeResolver),确保IUser能被序列化为包含完整字段的JSON。 - 优先使用
[JsonInclude]替代公开属性setter,既能保证序列化器正常工作,又能维持类的封装性。
内容的提问来源于stack exchange,提问作者Dario Stromajer
相关产品推荐
相关产品推荐

