因为jsonconverter是无泛型抽象基类,无法参与类型推导和编译检查;而jsonconverter是具体入口,支持精准类型匹配、编译时验证及read/write中typetoconvert的正确使用,避免notsupportedexception或转换器被跳过。

为什么继承 JsonConverter<t></t> 而不是直接写 JsonConverter
因为 JsonConverter 是抽象基类,没有泛型参数,无法参与类型推导和编译时检查;而 JsonConverter<t></t> 是具体实现入口,System.Text.Json 在序列化时靠它精准匹配类型。不指定 T,Read 和 Write 方法里的 typeToConvert 参数就失去意义,运行时容易报 NotSupportedException 或跳过你的转换器。
常见错误现象:注册了转换器但毫无效果,JSON 仍按默认规则处理——大概率是没继承 JsonConverter<yourtype></yourtype>,而是误写了 JsonConverter。
- 必须显式指定泛型类型,如
JsonConverter<datetime></datetime>、JsonConverter<dictionary string>></dictionary> - 若类型含泛型参数(如
Dictionary<tkey tvalue></tkey>),不能直接继承JsonConverter<dictionary tvalue>></dictionary>,得用JsonConverterFactory动态构造 -
Read方法里拿到的ref Utf8JsonReader必须手动推进;漏掉reader.Read()或误调reader.Skip()会导致后续字段解析错位
如何正确处理 null 值和意外 token 类型
System.Text.Json 不会帮你兜底:遇到 null、Number 却期待 String、或字段缺失时,Utf8JsonReader 的 TokenType 就是你唯一的判断依据。不检查,GetString() 会抛 InvalidOperationException,GetInt32() 会崩在 "true" 上。
典型场景:反序列化一个可为空的自定义对象,或兼容前后端字段类型不一致(比如后端传了 "123" 字符串,但 C# 属性是 int?)。
- 先读
reader.TokenType:等于JsonTokenType.Null就直接 return null(对可空类型)或 throw(对非空类型) - 再判断是否为预期类型,例如要读字符串,就检查
reader.TokenType == JsonTokenType.String,否则 throw 或 fallback 处理 - 用
reader.TryGet...()系列方法(如TryGetInt32(out int v))比直接取值更安全,失败时不抛异常 - 手动推进 reader:每次成功读一个值后,务必调用
reader.Read()进入下一个 token,否则下次Read()会卡在原地
什么时候必须用 JsonConverterFactory 而不是普通 JsonConverter<t></t>
当你需要为开放泛型类型(如 Dictionary<myenum t></myenum>、List<t></t>)提供通用转换逻辑时,JsonConverterFactory 是唯一选择。普通 JsonConverter<t></t> 只能绑定到闭合类型(如 Dictionary<status string></status>),编译期就固定死了。
错误做法:试图写 class DictEnumConverter : JsonConverter<dictionary tvalue>></dictionary> —— 编译不过,且运行时无法实例化。
-
CanConvert(Type typeToConvert)必须严格判断:检查IsGenericType、GetGenericTypeDefinition()、泛型参数数量和约束(如第一个是不是Enum) -
CreateConverter(Type type, JsonSerializerOptions options)里用Type.MakeGenericType(...)构造闭合类型,再通过Activator.CreateInstance创建对应转换器实例 - 内部实际转换器(如
DictionaryEnumConverterInner<tkey tvalue></tkey>)仍需继承JsonConverter<dictionary tvalue>></dictionary>,只是它的TKey和TValue在工厂里动态确定 - 别忘了在
CreateConverter中复用options:把options传给内部转换器,让它能获取其他已注册的 converter(比如处理TValue的 converter)
[JsonConverter] 特性与全局注册的优先级冲突怎么解
特性方式永远高于全局注册。如果你在某个属性上加了 [JsonConverter(typeof(MyConverter))],哪怕你同时在 JsonSerializerOptions.Converters 里加了另一个转换器,也只会走特性的那个。
这在调试时容易误判:以为全局注册生效了,结果发现某字段行为异常——其实是类/属性上的特性悄悄覆盖了。
- 特性可作用于三处:类(整个类型)、属性(仅该字段)、泛型类型参数(C# 12+ 支持)
- 如果类和属性都标了不同转换器,属性级优先级更高
- 工厂类(
JsonConverterFactory)也能被特性引用,如[JsonConverter(typeof(DictionaryEnumConverterFactory))] - ASP.NET Core 全局配置(
AddJsonOptions)注册的转换器,对带特性的成员完全无效——这是设计使然,不是 bug
真正难的不是写出 Read 和 Write,而是每次推进 Utf8JsonReader 前,都想清楚当前 token 是什么、下一个应该是什么、null 怎么退、异常怎么抛。System.Text.Json 没有“上下文自动恢复”机制,错一步,整段 JSON 解析就偏移了。











