C#中Newtonsoft.Json 到 System.Text.Json 迁移避坑指南

 更新时间:2026年03月05日 09:09:16   作者:xyy123  
本文主要介绍了C#中Newtonsoft.Json 到 System.Text.Json 迁移避坑指南,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

1. 核心设计哲学差异

在进行代码迁移前,必须牢记这两个库在底层设计哲学上的根本分歧,这是几乎所有反序列化报错的根源:

  • Newtonsoft.Json (主打兼容与灵活) :它非常宽容,会尽最大努力去猜测你的意图,在底层默默帮你做各种隐式的类型转换和容错处理。
  • System.Text.Json (主打性能与安全) :微软为了追求极致的执行效率而原生打造。它极其严格,要求 JSON 数据结构和 C# 模型“严丝合缝”,绝不会越界替你做任何类型转换。

2. 基础特性与配置替换对照表

场景Newtonsoft.Json (旧)System.Text.Json (新)迁移备注
指定 JSON 键名[JsonProperty("name")][JsonPropertyName("name")]必须逐个替换。
忽略某字段[JsonIgnore][JsonIgnore]基本一致。
忽略空值 (Null)NullValueHandling.Ignore全局 Options: DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull推荐在全局 JsonSerializerOptions 中统一配置,减少序列化体积。
忽略默认值DefaultValueHandling.Ignore[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]新版将其合并到了 JsonIgnore 特性中。

3. 四大高频“踩坑”重灾区及标准解决方案

🚨 坑一:大小写严格敏感 (Case Sensitivity)

问题描述:很多第三方 API 返回的小驼峰命名(如 userProfile),而 C# 模型是大驼峰命名(如 UserProfile)。老版能完美自动映射,新版只要大小写不一致,直接反序列化为 null

解决方案:在反序列化时,务必全局传入配置允许忽略大小写:

var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var result = JsonSerializer.Deserialize<MyModel>(jsonString, options);

🚨 坑二:基础类型严格匹配(数字与字符串的鸿沟)

问题描述:对接外部不可控 API 时经常遇到格式不规范的数据。比如 ID 字段有时是数字 ("id": 123),有时是字符串 ("id": "A-123");金额字段有时返回字符串 ("price": "19.99"),甚至用空字符串表示无数据 ("discount": "")。

新版只要遇到 JSON 节点类型与 C# 声明类型(如 stringint)不匹配,会直接抛出 JsonException 崩溃。

解决方案:不要指望内置配置项能完美兜底(尤其是处理空字符串),建议直接封装自定义 JsonConverter

🛠️ 通用工具 1:数字安全转字符串转换器 (NumberToStringConverter)

用途:当 C# 模型定义为 string Id,但外部 JSON 传入的是数字 123 时,自动将其转换为 "123" 且不报错。

using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace YourNamespace.Helpers 
{
    public class NumberToStringConverter : JsonConverter<string?>
    {
        public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            if (reader.TokenType == JsonTokenType.Number) return reader.GetInt64().ToString(); 
            if (reader.TokenType == JsonTokenType.String) return reader.GetString();
            return null;
        }

        public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerOptions options)
        {
            if (value == null) writer.WriteNullValue();
            else writer.WriteStringValue(value);
        }
    }
}
// 实体类使用方式:[JsonConverter(typeof(NumberToStringConverter))]

🛠️ 通用工具 2:字符串安全转可空金额转换器 (StringToDecimalConverter)

用途:当 C# 模型定义为 decimal? Price,但 JSON 传入的是 "19.99" 或者是代表无值的空字符串 "" 时,安全地将其转换为 decimalnull

using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace YourNamespace.Helpers 
{
    // 注意:泛型必须与属性类型完全一致(此处为可空类型 decimal?)
    public class StringToDecimalConverter : JsonConverter<decimal?>
    {
        public override decimal? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            if (reader.TokenType == JsonTokenType.Number) return reader.GetDecimal();
            if (reader.TokenType == JsonTokenType.String)
            {
                string? strValue = reader.GetString();
                // 很多老旧 API 喜欢用空字符串代表没有值,安全处理为 null
                if (string.IsNullOrWhiteSpace(strValue)) return null;
                if (decimal.TryParse(strValue, out decimal result)) return result;
            }
            return null;
        }

        public override void Write(Utf8JsonWriter writer, decimal? value, JsonSerializerOptions options)
        {
            // 序列化时,可根据对接方 API 的偏好决定是否转回字符串
            if (value.HasValue) writer.WriteStringValue(value.Value.ToString("0.00"));
            else writer.WriteNullValue();
        }
    }
}
// 实体类使用方式:[JsonConverter(typeof(StringToDecimalConverter))]
// 警告:此转换器必须配合 public decimal? Price { get; set; } 使用!不可用于非空 decimal。

🚨 坑三:动态类型object的解析陷阱

问题描述:当模型中存在 public object Value { get; set; }(例如用于接收不确定结构的数据、扩展字段 metadata 等),老版会猜测并转化为 string, int 等具体 C# 基础类型。而新版会统一将其解析为 JsonElement 结构体

危险操作:任何试图将反序列化后的 object 强转回基础类型的操作都会导致运行时崩溃!

  • string val = (string)model.Value; -> 抛出 InvalidCastException
  • if (model.Value is string s) -> 永远为 false
  • string val = model.Value as string; -> 永远返回 null

安全的操作规范(提取真实数据)

  1. 纯中转/序列化/拼接场景(最稳妥) :利用 Convert.ToString() 提取字面量。

    // 完美应对 JsonElement。
    // 配合 InvariantCulture 防止部署在不同国家服务器时,小数点被转换成逗号的问题。
    string safeStringValue = Convert.ToString(model.Value, CultureInfo.InvariantCulture)!;
    
  2. 业务逻辑需严格执行类型判断:通过检查 JsonElement.ValueKind

    if (model.Value is JsonElement element)
    {
        if (element.ValueKind == JsonValueKind.String) 
            string s = element.GetString();
        else if (element.ValueKind == JsonValueKind.Number) 
            decimal d = element.GetDecimal();
    }
    

🚨 坑四:字段 (Fields) 被静默忽略

问题描述:老版会自动序列化和反序列化 public string name; 这种公开的字段 (Fields)。新版默认只处理属性 (Properties) ,即带有 { get; set; } 的成员,对字段直接静默忽略,不报错但数据会全部丢失。

解决方案

  1. 最佳实践:将实体类的成员强制重构为标准属性 { get; set; }

  2. 兼容方案:若存在大量历史代码难以修改,需在全局 Options 中显式开启:

    var options = new JsonSerializerOptions { IncludeFields = true };
    

4. 迁移与调试的黄金法则

在未来遇到 System.Text.Json 抛出异常或反序列化出 null 时,请严格遵循以下排查步骤:

  1. 绝不盲猜数据结构:不要依赖 API 文档。务必将 response.Content.ReadAsStreamAsync() 临时替换为 ReadAsStringAsync(),把原始 JSON 字符串完整打印到日志中,肉眼确认真实的层级和数据格式。
  2. 检查大小写配置:确认代码中是否遗漏了 PropertyNameCaseInsensitive = true 的配置。
  3. 检查类型严格性:排查 JSON 里的 "123" 和 C# 里的 int 是否发生了直接碰撞,若有,必须引入 Converter。
  4. 检查转换器泛型匹配:贴在可空类型属性上的转换器,其继承的基类绝对不能是非空类型(如 JsonConverter<decimal?> 绝不能用于 decimal 属性),必须分毫不差。

到此这篇关于C#中Newtonsoft.Json 到 System.Text.Json 迁移避坑指南的文章就介绍到这了,更多相关C#中Newtonsoft.Json到System.Text.Json 迁移内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C#邮件发送与附件处理过程详解

    C#邮件发送与附件处理过程详解

    文章介绍了邮件发送的基本概念、SMTP协议、常见SMTP服务器配置、C#邮件发送的核心类库和实现步骤,通过实例分析,展示了如何封装邮件发送工具类以及在实际业务中的应用,需要的朋友可以参考下
    2026-01-01
  • C#提示:“在证书存储区中找不到清单签名证书”的解决方法

    C#提示:“在证书存储区中找不到清单签名证书”的解决方法

    这篇文章主要介绍了C#提示:“在证书存储区中找不到清单签名证书”的解决方法,分析了几种常见的解决方案供大家选择使用,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-01-01
  • C#高效实现Word文档内容查找与替换的6种方法

    C#高效实现Word文档内容查找与替换的6种方法

    在日常文档处理工作中,尤其是面对大型 Word 文档时,手动查找、替换文本往往既耗时又容易出错,本文整理了C#查找与替换Word内容的6种方法,大家可以根据需要进行选择
    2025-08-08
  • C#中实现抽象类里建立静态方法

    C#中实现抽象类里建立静态方法

    这篇文章主要介绍了C#中实现抽象类里建立静态方法,需要的朋友可以参考下
    2014-07-07
  • C#多线程经典示例(吃苹果)

    C#多线程经典示例(吃苹果)

    本文主要讲述了多线程开发中经典示例,通过本示例,可以加深对多线程的理解。下面跟着小编一起来看下吧
    2017-01-01
  • C#通过HSLCommunication库操作PLC用法

    C#通过HSLCommunication库操作PLC用法

    本文主要介绍了C#通过HSLCommunication库操作PLC用法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2026-01-01
  • C# EventHandler的使用详解(最新整理)

    C# EventHandler的使用详解(最新整理)

    在C#中,EventHandler是一种特殊的委托类型,专门用于事件处理,它定义在System命名空间中,并且通常用来实现发布-订阅模式,这是 .NET 框架中处理事件的标准方式,下面通过本文给大家介绍C# EventHandler的使用,感兴趣的朋友跟随小编一起看看吧
    2025-10-10
  • C#学习基础概念二十五问

    C#学习基础概念二十五问

    C#学习基础概念二十五问...
    2007-04-04
  • c# 颜色选择控件的实现代码

    c# 颜色选择控件的实现代码

    这篇文章主要介绍了c# 颜色选择控件的实现代码,帮助大家更好的理解和学习使用c#,感兴趣的朋友可以了解下
    2021-04-04
  • C#使用LINQ查询操作符实例代码(一)

    C#使用LINQ查询操作符实例代码(一)

    这篇文章介绍了C#使用LINQ查询操作符的方法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-06-06

最新评论