如何通过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
相关产品推荐
相关产品推荐

