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

如何通过Json.NET简便实现自定义类型元数据处理?

替换Newtonsoft.Json默认类型元数据键的通用方案

针对跨服务共享Redis存储时,默认$type元数据依赖命名空间导致的反序列化问题,无需重写完整序列化逻辑的通用方案如下:

核心思路

复用Newtonsoft.Json原生序列化机制,通过自定义契约解析器添加自定义元数据键(__type和__namespace),配合类型绑定器和轻量转换器实现类型信息的读写,避免重复造轮子且保证性能。

1. 自定义类型绑定器(处理类型与元数据的映射)

负责在序列化时拆分类型的命名空间和类名,反序列化时根据这两个字段拼接并找到目标类型:

public class CustomTypeBinder : ISerializationBinder
{
    // 反序列化:根据命名空间和类名查找类型
    public Type BindToType(string assemblyName, string typeName)
    {
        var fullTypeName = $"{assemblyName}.{typeName}";
        // 可扩展:添加类型映射逻辑(如不同服务的命名空间别名)
        return Type.GetType(fullTypeName, throwOnError: true);
    }

    // 序列化:拆分类型的命名空间和类名
    public void BindToName(Type serializedType, out string assemblyName, out string typeName)
    {
        assemblyName = serializedType.Namespace;
        typeName = serializedType.Name;
    }
}

2. 自定义契约解析器(自动添加自定义元数据字段)

重写默认契约解析器,为目标类型自动添加__type和__namespace只读属性,序列化时自动填充值:

public class CustomTypeContractResolver : DefaultContractResolver
{
    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        var contract = base.CreateObjectContract(objectType);

        // 添加__type属性(存储类名)
        contract.Properties.Add(new JsonProperty
        {
            PropertyName = "__type",
            Readable = true,
            Writable = false,
            PropertyType = typeof(string),
            ValueProvider = new StaticValueProvider(objectType.Name)
        });

        // 添加__namespace属性(存储命名空间)
        contract.Properties.Add(new JsonProperty
        {
            PropertyName = "__namespace",
            Readable = true,
            Writable = false,
            PropertyType = typeof(string),
            ValueProvider = new StaticValueProvider(objectType.Namespace)
        });

        return contract;
    }
}

// 静态值提供器:为自定义元数据属性返回固定值
public class StaticValueProvider : IValueProvider
{
    private readonly string _fixedValue;

    public StaticValueProvider(string fixedValue)
    {
        _fixedValue = fixedValue;
    }

    public object GetValue(object target) => _fixedValue;
    public void SetValue(object target, object value) { } // 反序列化时无需设置
}

3. 轻量类型转换器(处理反序列化的类型解析)

仅负责反序列化时读取自定义元数据,找到目标类型后复用原生反序列化逻辑:

public class CustomTypeConverter : JsonConverter
{
    private readonly ISerializationBinder _typeBinder;

    public CustomTypeConverter(ISerializationBinder typeBinder)
    {
        _typeBinder = typeBinder;
    }

    // 控制哪些类型需要用此转换器(按需调整,如基类/接口或所有引用类型)
    public override bool CanConvert(Type objectType)
    {
        return typeof(YourSharedBaseType).IsAssignableFrom(objectType) || !objectType.IsValueType;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var jObject = JObject.Load(reader);
        
        // 读取自定义元数据
        var typeName = jObject["__type"]?.Value<string>();
        var namespaceName = jObject["__namespace"]?.Value<string>();

        if (string.IsNullOrEmpty(typeName) || string.IsNullOrEmpty(namespaceName))
        {
            // 无类型信息时,直接反序列化为原始类型
            return jObject.ToObject(objectType, serializer);
        }

        // 通过绑定器获取目标类型并反序列化
        var targetType = _typeBinder.BindToType(namespaceName, typeName);
        return jObject.ToObject(targetType, serializer);
    }

    // 序列化时直接复用原生逻辑(契约解析器已添加元数据)
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, value);
    }
}

4. 配置序列化设置

将上述组件整合到JsonSerializerSettings中,替换默认行为:

var typeBinder = new CustomTypeBinder();
var serializerSettings = new JsonSerializerSettings
{
    ContractResolver = new CustomTypeContractResolver(),
    Converters = new List<JsonConverter> { new CustomTypeConverter(typeBinder) },
    TypeNameHandling = TypeNameHandling.None, // 禁用默认$type输出
    Formatting = Formatting.Indented // 按需调整格式化方式
};

// 序列化示例
var model = new YourSharedModel();
string json = JsonConvert.SerializeObject(model, serializerSettings);

// 反序列化示例
var deserializedModel = JsonConvert.DeserializeObject<YourSharedBaseType>(json, serializerSettings);

方案优势

  • 复用Newtonsoft原生序列化逻辑,性能接近原生实现,避免自定义Converter带来的性能损耗
  • 自定义元数据键脱离对$type的依赖,解决跨服务命名空间不一致的问题
  • 扩展性强:可在CustomTypeBinder中添加类型别名、跨服务命名空间映射等逻辑

注意事项

  • 调整CustomTypeConverter.CanConvert方法的逻辑,确保覆盖所有需要跨服务共享的类型
  • 若不同服务的程序集名称存在差异,可在CustomTypeBinder.BindToType中添加程序集名称的映射逻辑
  • 如需仅为特定类型添加元数据,可在CustomTypeContractResolver.CreateObjectContract中添加类型判断逻辑

内容的提问来源于stack exchange,提问作者Sahan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 07:25:00