导读:本期聚焦于梁博渊创作的《.NET Core JSON序列化报错怎么办?System.Text.Json与Newtonsoft.Json兼容实战指南》,敬请观看详情。为什么项目从.NET Framework迁移到.NET Core之后,原本好用的JSON序列化突然出问题了?System.Text.Json虽然性能出色,但默认大小写敏感、不支持字段序列化、枚举和日期格式处理也和Newtonsoft.Json差异明显,导致接口返回数据结构变化或反序列化失败。本文详细对比两套序列化组件在命名策略、空值处理、多态支持、循环引用等场景下的行为差异,给出具体的JsonSerializerOptions配置方法,并演示如何通过自定义Converter和全局注册让新旧代码平滑共存,帮助你在迁移过程中少踩坑、快速定位序列化异常的根因。

把一个老项目从.NET Framework迁到.NET Core,最容易翻车的环节往往不是依赖包,而是JSON序列化。ASP.NET Core默认换成了System.Text.Json,很多接口的返回结果直接变了样:字段首字母变小写、日期格式不对、枚举变成了数字,甚至反序列化直接抛异常。这篇文章就来把System.Text.Json和Newtonsoft.Json的差异讲清楚,并给出几种实用的兼容方案。

.NET Core JSON序列化报错怎么办?System.Text.Json与Newtonsoft.Json兼容实战指南

一、两者的核心差异到底在哪里

System.Text.Json是微软为了性能重新打造的一套序列化库,设计上偏严格和保守,很多Newtonsoft.Json里的“默认宽容行为”都被砍掉了。最典型的就是命名策略:System.Text.Json默认按属性原名输出(PascalCase),而Newtonsoft.Json在ASP.NET Core的Web场景里,默认使用camelCase,属性UserName会被序列化成userName。前端拿到的字段名变了,界面上的数据绑定自然就挂了。

第二个差异是大小写敏感性。Newtonsoft.Json反序列化时默认不区分大小写,而System.Text.Json在查找属性时严格区分大小写,前端传的username匹配不到UserName属性就直接丢弃,而且不报错,这类问题排查起来特别隐蔽。第三个差异是功能支持,早期版本不支持字段序列化、不支持循环引用处理、不支持JsonIgnoreCondition的灵活配置、对Dictionary的非字符串key处理也不一样。虽然新版本逐步补齐了能力,但默认行为依旧不同。

还有日期和枚举的处理。Newtonsoft.Json默认把DateTime序列化成ISO 8601格式的本地时间字符串,而System.Text.Json严格按照ISO 8601-1标准输出,时区后缀、精度上都有细微差别。枚举方面,两者默认都输出数字,但Newtonsoft.Json提供StringEnumConverter全局转换很方便,System.Text.Json需要显式配置JsonStringEnumConverter。下面这张表总结了常见差异:

特性Newtonsoft.JsonSystem.Text.Json
属性命名Web默认camelCase默认保持原名
反序列化大小写不敏感敏感(可配置)
字段(Field)序列化默认支持需设置IncludeFields
循环引用默认支持(含$id)需显式开启ReferenceHandler
忽略null属性NullValueHandling.IgnoreDefaultIgnoreCondition

二、通过配置让System.Text.Json行为对齐Newtonsoft.Json

如果打算留在System.Text.Json体系里,最干净的做法是用JsonSerializerOptions把各项行为调整到和Newtonsoft.Json一致。在ASP.NET Core中,可以在Program.cs里全局配置,这样所有Controller的输入输出都会走同一套规则,不需要在每个类上加特性。

builder.Services.AddControllers()
    .AddJsonOptions(options =>
    {
        var jsonOptions = options.JsonSerializerOptions;
        // 属性名转为camelCase,与Newtonsoft.Json Web默认行为一致
        jsonOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
        // 反序列化时属性名不区分大小写
        jsonOptions.PropertyNameCaseInsensitive = true;
        // 序列化公共字段
        jsonOptions.IncludeFields = true;
        // 枚举序列化为字符串
        jsonOptions.Converters.Add(new JsonStringEnumConverter());
        // 忽略值为null的属性
        jsonOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
        // 处理循环引用
        jsonOptions.ReferenceHandler = ReferenceHandler.Preserve;
        // 允许注释和尾随逗号,解析更宽容
        jsonOptions.ReadCommentHandling = JsonCommentHandling.Skip;
        jsonOptions.AllowTrailingCommas = true;
    });

需要注意几点细节。ReferenceHandler.Preserve会在输出里加上$id$values这样的元数据字段,如果前端是全新对接的还好,但如果是老系统对接,这些额外的字段可能导致解析失败,这时要评估是否真的存在循环引用场景,没有就干脆不开启,让DTO设计保持树形结构。

另外,枚举的字符串化还有个坑:如果枚举成员带有EnumMember或自定义名称,JsonStringEnumConverter默认按成员原名输出,可以用它的构造函数指定命名策略,例如new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)。日期格式如果需要完全自定义,比如输出yyyy-MM-dd HH:mm:ss这种中文系统常见的格式,就得写自定义Converter,后面会讲到写法。

三、直接换回Newtonsoft.Json:全局注册方案

如果项目里存在大量依赖Newtonsoft.Json特性的遗留代码,比如JsonPropertyJsonConverter特性满天飞,逐个改造的成本太高,更务实的方案是让ASP.NET Core整体切换回Newtonsoft.Json。微软官方提供了兼容包,安装后在管道里替换即可。

// 安装包:Microsoft.AspNetCore.Mvc.NewtonsoftJson
builder.Services.AddControllers()
    .AddNewtonsoftJson(options =>
    {
        options.SerializerSettings.ContractResolver =
            new CamelCasePropertyNamesContractResolver();
        // 忽略null属性
        options.SerializerSettings.NullValueHandling =
            NullValueHandling.Ignore;
        // 日期格式
        options.SerializerSettings.DateFormatString =
            "yyyy-MM-dd HH:mm:ss";
        // 枚举转字符串
        options.SerializerSettings.Converters.Add(
            new StringEnumConverter());
    });

这个方案的优点是迁移成本几乎为零,老代码里的特性、自定义Converter、序列化设置都能原样工作。缺点是性能上不如System.Text.Json,尤其是高并发场景下差距会被放大;同时Newtonsoft.Json的维护重心已经转向维护模式,新特性基本不会再加,长远来看还是要逐步迁移。

还有一点容易被忽略:混用两个库时要小心命名空间冲突。一个项目里既有Newtonsoft.Json.JsonProperty又有System.Text.Json.Serialization.JsonPropertyName时,两个库互不识别对方的特性,同一个类在两套序列化器下行为可能完全不同。建议通过文件级命名空间别名或者统一项目规范来约束,避免同一个DTO被两条序列化管线处理出两种结果。

四、自定义Converter处理特殊类型

无论选哪套方案,总会遇到内置能力覆盖不了的场景,比如把DateTime输出成yyyy-MM-dd HH:mm:ss、把decimal统一保留两位小数、给long类型防精度丢失转字符串等。System.Text.Json提供了JsonConverter<T>基类,实现起来并不复杂。

public class DateTimeFormatConverter : JsonConverter<DateTime>
{
    private const string Format = "yyyy-MM-dd HH:mm:ss";

    public override DateTime Read(
        ref Utf8JsonReader reader,
        Type typeToConvert,
        JsonSerializerOptions options)
    {
        return DateTime.Parse(reader.GetString());
    }

    public override void Write(
        Utf8JsonWriter writer,
        DateTime value,
        JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString(Format));
    }
}

// 使用方式一:全局注册
options.Converters.Add(new DateTimeFormatConverter());

// 使用方式二:针对单个属性标注
public class Order
{
    [JsonConverter(typeof(DateTimeFormatConverter))]
    public DateTime CreateTime { get; set; }
}

写自定义Converter时有几个注意点。Read方法里拿到的是原始token,务必做好异常处理,因为输入数据不受你控制,一个格式错误的日期就会让整个反序列化失败,建议捕获后抛出JsonException,让框架按标准的反序列化错误流程处理。另外,从.NET 6开始,如果只需要简单包装,可以使用工厂模式的Converter,避免为每个泛型类型都写一个类。

对于Newtonsoft.Json,对应的机制是继承JsonConverter并重写ReadJsonWriteJson,思路完全一致,只是API形态不同。如果团队里两套库并存,可以考虑把特殊类型的转换逻辑抽到公共层,用适配器分别包装,这样切换底层库时业务逻辑不用动。

五、迁移建议与排查思路

结合实际项目经验,给出一个推荐路径:新项目直接用System.Text.Json并按需配置,不要为了习惯去装Newtonsoft.Json;存量项目迁移时,可以先用AddNewtonsoftJson整体切换保证功能不回退,然后按模块逐步改造DTO,用JsonPropertyName替代JsonProperty,用JsonConverter替代Newtonsoft的自定义转换器,最后移除依赖。

排查序列化问题时,优先对比原始JSON字符串而不是看前端表现。可以用JsonSerializer.Serialize在单元测试里直接输出结果,和迁移前的报文做diff,字段名、大小写、日期格式、null处理这几项差异一眼就能看出来。循环引用导致的栈溢出、多态反序列化拿不到具体类型(System.Text.Json需要配合JsonDerivedType特性声明子类),这两个是迁移中报错率最高的问题,提前在测试环境覆盖到。

总的来说,System.Text.Json和Newtonsoft.Json的兼容问题不难解,关键是分清场景:要么通过配置对齐行为,要么整体替换序列化器,再辅以自定义Converter处理特殊类型。把序列化规则统一收敛到全局配置层,避免到处散落特性标注,后续的维护成本会低很多。

System.Text.JsonNewtonsoft.JsonJSON序列化修改时间:2026-09-05 01:56:41

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260905/50603.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。