如何基于枚举类型反序列化含抽象类的复杂嵌套JSON?
基于枚举类型信息反序列化嵌套JSON到抽象类派生对象
问题背景
我需要将一段复杂的嵌套JSON反序列化为自定义对象,其中涉及抽象类型,必须根据JSON中枚举存储的类型信息,匹配对应的派生类完成实例化。以下示例仅展示一层嵌套,实际场景中嵌套层级更多,包含的对象与类型也更复杂。
JSON示例
{ "screen":{ "type":"Component", "footer":{ "type":"Bar" }, "header":{ "type":"Top" } } }
对应C#类定义
public abstract class Screen { public abstract ScreenType Type { get; } } public enum ScreenType { Component, b, c, d, e } public sealed class ComponentScreen : Screen { public override ScreenType Type => ScreenType.Component; public Header? Header { get; init; } public Footer? Footer { get; init; } public bool? ShowStuff { get; init; } } public interface ITyped<TEnum> where TEnum : Enum { TEnum Type { get; } } public abstract class Header : ITyped<HeaderType> { public abstract HeaderType Type { get; } } public enum HeaderType { Top, b, c, d } public sealed class TopScreenHeader : Header { public override HeaderType Type => HeaderType.Top; public string MyStuff { get; } }
当前尝试的代码及错误
我原本使用Newtonsoft.Json的TypeNameHandling来处理,但运行时报错:
var screen = JsonConvert.DeserializeObject<Screen>(jsonString, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Objects, ContractResolver = new CamelCasePropertyNamesContractResolver() });
错误信息:
Could not create an instance of type Screens.Screen. Type is an interface or abstract class and cannot be instantiated. Path 'screen', line 1, position 10.
这是因为TypeNameHandling依赖JSON中的$type字段识别类型,而我们的JSON用的是自定义type枚举字段,无法自动匹配派生类。
解决方案
我们可以自定义通用枚举类型转换器,通过读取JSON中的type枚举值,映射到对应的派生类完成反序列化,既不用修改抽象类,也能避免重复编写大量转换器。
1. 通用枚举类型转换器
先实现一个通用转换器基类,复用核心逻辑:
public class TypedEnumConverter<TBase, TEnum> : JsonConverter<TBase> where TBase : class, ITyped<TEnum> where TEnum : Enum { private readonly Dictionary<TEnum, Type> _typeMap; public TypedEnumConverter(Dictionary<TEnum, Type> typeMap) { _typeMap = typeMap ?? throw new ArgumentNullException(nameof(typeMap)); } public override TBase ReadJson(JsonReader reader, Type objectType, TBase existingValue, bool hasExistingValue, JsonSerializer serializer) { var jObject = JObject.Load(reader); var typeEnum = jObject["type"].ToObject<TEnum>(); if (!_typeMap.TryGetValue(typeEnum, out var targetType)) throw new JsonSerializationException($"未知{typeof(TEnum).Name}类型: {typeEnum}"); return (TBase)jObject.ToObject(targetType, serializer); } public override void WriteJson(JsonWriter writer, TBase value, JsonSerializer serializer) { serializer.Serialize(writer, value); } }
2. 配置类型映射与反序列化
针对每个抽象类,定义枚举到派生类的映射,然后配置转换器进行反序列化:
// 定义Screen的类型映射 var screenTypeMap = new Dictionary<ScreenType, Type> { { ScreenType.Component, typeof(ComponentScreen) }, // 其他ScreenType对应的派生类 }; // 定义Header的类型映射 var headerTypeMap = new Dictionary<HeaderType, Type> { { HeaderType.Top, typeof(TopScreenHeader) }, // 其他HeaderType对应的派生类 }; // 配置序列化设置 var settings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver(), Converters = new List<JsonConverter> { new TypedEnumConverter<Screen, ScreenType>(screenTypeMap), new TypedEnumConverter<Header, HeaderType>(headerTypeMap) // 其他抽象类的转换器 } }; // 方式一:通过根对象类反序列化 public class RootObject { public Screen Screen { get; set; } } var root = JsonConvert.DeserializeObject<RootObject>(jsonString, settings); var screen = root.Screen; // 方式二:直接提取screen节点反序列化 var jRoot = JObject.Parse(jsonString); var screenJson = jRoot["screen"].ToString(); var screen = JsonConvert.DeserializeObject<Screen>(screenJson, settings);
说明
- 通用转换器
TypedEnumConverter可复用在所有实现ITyped<TEnum>接口的抽象类上,只需传入对应的类型映射字典。 - 转换器自动读取JSON中的
type字段,解析为枚举值后匹配到对应派生类,完成反序列化。 - 新增抽象类(如
Footer)时,只需补充对应的类型映射和转换器实例,无需重复编写反序列化逻辑。
内容的提问来源于stack exchange,提问作者Jan
相关产品推荐
相关产品推荐

