You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

这是更直观可控的方案,直接实现接口类型的序列化/反序列化逻辑:

  1. 在客户端库中创建转换器:
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);
    }
}
  1. 在客户端的序列化配置中注册转换器:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 12:05:30