ASP.NET Core 5:如何用System.Text.Json序列化Newtonsoft JToken
问题根源
System.Text.Json 对Json.NET的JObject类型没有内置兼容逻辑,会将其当作普通POCO遍历公开属性(如索引器、元素计数属性)序列化,完全忽略其树形JSON结构,最终输出无意义的嵌套空数组。
实现方案
完全匹配约束要求:
- 不全局切换回Json.NET序列化
- 不执行JObject转字符串、再解析为STJ JsonDocument的重复开销操作
- 全流式逐节点写入,无中间大对象内存分配,适配大体积JObject的性能要求
只需为System.Text.Json实现针对JToken类型的自定义转换器即可:
1. 编写自定义JToken转换器
直接递归遍历JToken的树形结构,逐节点写入STJ的Utf8JsonWriter,全程无中间序列化/解析步骤:
using System.Globalization; using System.Numerics; using System.Text.Json; using System.Text.Json.Serialization; using Newtonsoft.Json.Linq; public class JTokenConverter : JsonConverter<JToken> { public override bool CanConvert(Type typeToConvert) { return typeof(JToken).IsAssignableFrom(typeToConvert); } public override JToken? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { // 当前场景仅需处理输出序列化,无需实现反序列化逻辑 throw new NotImplementedException(); } public override void Write(Utf8JsonWriter writer, JToken value, JsonSerializerOptions options) { switch (value.Type) { case JTokenType.Null: writer.WriteNullValue(); break; case JTokenType.Boolean: writer.WriteBooleanValue((bool)((JValue)value).Value!); break; case JTokenType.Integer: var intVal = ((JValue)value).Value!; switch (intVal) { case int i: writer.WriteNumberValue(i); break; case long l: writer.WriteNumberValue(l); break; case BigInteger bi: writer.WriteNumberValue(bi); break; } break; case JTokenType.Float: var floatVal = ((JValue)value).Value!; switch (floatVal) { case float f: writer.WriteNumberValue(f); break; case double d: writer.WriteNumberValue(d); break; case decimal dec: writer.WriteNumberValue(dec); break; } break; case JTokenType.String: case JTokenType.Date: case JTokenType.Guid: case JTokenType.Uri: case JTokenType.TimeSpan: writer.WriteStringValue(Convert.ToString(((JValue)value).Value, CultureInfo.InvariantCulture)); break; case JTokenType.Array: writer.WriteStartArray(); foreach (var item in (JArray)value) { Write(writer, item, options); } writer.WriteEndArray(); break; case JTokenType.Object: writer.WriteStartObject(); foreach (var prop in (JObject)value.Properties()) { writer.WritePropertyName(prop.Name); Write(writer, prop.Value, options); } writer.WriteEndObject(); break; case JTokenType.Property: var prop = (JProperty)value; writer.WritePropertyName(prop.Name); Write(writer, prop.Value, options); break; default: writer.WriteStringValue(value.ToString()); break; } } }
2. 注册转换器到ASP.NET Core的STJ配置
仅在默认的System.Text.Json序列化选项中注入该转换器,不修改全局序列化逻辑,也不会切换为Json.NET处理所有接口输出:
// 在Startup.ConfigureServices中配置MVC序列化选项 services.AddControllers() .AddJsonOptions(jsonOptions => { jsonOptions.JsonSerializerOptions.Converters.Add(new JTokenConverter()); });
效果验证
配置完成后运行测试用例:
- 输出JSON与原始
JObject内容完全一致,满足JToken.DeepEquals校验要求 - 整个序列化过程无中间字符串分配、无重复JSON解析开销,大体积JObject的序列化性能与原生STJ处理基本持平
- 对非JToken类型的序列化逻辑完全无侵入,不影响原有项目的STJ配置
内容的提问来源于stack exchange,提问作者dbc
相关产品推荐
相关产品推荐

