C# JSON 处理实战:Newtonsoft.Json 与 System.Text.Json 选型、避坑与性能调优

📅 发布时间:2026/10/18 10:09:34
C# JSON 处理实战:Newtonsoft.Json 与 System.Text.Json 选型、避坑与性能调优
简介JsonDemo.rar 是一份面向 C# 开发者、尤其是初涉 .NET 数据交互场景的学习者准备的 JSON 处理示例工程围绕 Newtonsoft.JsonJson.NET库展开帮助读者理解序列化、反序列化、动态解析与自定义序列化行为等核心用法。压缩包共 31 个文件约 3.58MB以 dll 与 xml 依赖库文件、cs 源码、config 配置、csproj 与 sln 工程文件为主另含 nupkg 包与 json 数据文件构成一个可直接打开运行的完整项目结构。资源中通过 Person、ParameterByJson 等类与 Program 入口演示了对象与 JSON 字符串的相互转换、JObject 动态取值以及 JsonProperty、JsonIgnore 等特性控制并借助 JsonSerializerSettings 展示日期格式与命名策略配置。目前已有 743 人学习下载适合希望快速上手 .NET 平台 JSON 处理、对照源码理解库 API 的开发者参考。1. 从一份 JsonDemo.rar 说起C# 里 JSON 处理到底该从哪下手如果你写过 C#大概率遇到过这种场景对接第三方接口返回一大坨 JSON字段嵌套三四层数组里还混着 null或者本地要存配置、存日志、存缓存用数据库太重用纯文本又不好解析。这时候 JSON 几乎是默认答案。但真到动手问题就来了——用Newtonsoft.Json还是System.Text.Json反序列化时字段名对不上怎么办日期格式、枚举、循环引用这些玄学问题怎么破这份 JsonDemo.rar 就是围绕这些真实痛点整理的一套 C# JSON 处理示例集合。它不厚但覆盖了序列化、反序列化、动态解析、配置读写、异常处理这几条主线适合刚接触 C# JSON 的新手照着跑一遍也适合有经验的开发者拿来当速查手册。下面我按「先跑通、再理解、最后避坑」的顺序把这份资源拆开讲清楚。2. 环境准备与两种主流库的选型Newtonsoft.Json 和 System.Text.Json 到底用哪个2.1 先确认你的 .NET 版本和项目类型拿到 JsonDemo.rar 后别急着双击运行先看项目文件。C# 的 JSON 生态和 .NET 版本强绑定选错库会直接编译不过。常见组合是这样的.NET 版本推荐 JSON 库说明.NET Framework 4.5 及以下Newtonsoft.Json内置没有 JSON 支持必须引第三方.NET Framework 4.6Newtonsoft.Json 或 System.Text.Json后者需通过 NuGet 安装.NET Core 3.1 / .NET 5System.Text.Json 优先官方内置性能更好.NET Standard 2.0两者都行看目标平台我一般会先打开.csproj文件看TargetFramework那一行。如果是net6.0、net8.0这种直接用System.Text.Json就行不用额外装包。如果是net48或者更老的老老实实上Newtonsoft.Json。提示JsonDemo.rar 里如果同时包含两种库的示例建议先跑和你项目版本匹配的那一套不要混着抄混用容易在命名空间上翻车。2.2 安装依赖NuGet 命令行和包管理器两种方式假设你决定用Newtonsoft.Json安装方式有两种。命令行适合自动化图形界面适合新手。# 方式一Package Manager Console 里执行 Install-Package Newtonsoft.Json -Version 13.0.3 # 方式二dotnet CLI.NET Core / .NET 5 项目 dotnet add package Newtonsoft.Json --version 13.0.3如果你用System.Text.Json.NET Core 3.0 以上是内置的不需要装。但如果你在 .NET Framework 上想用它得额外装dotnet add package System.Text.Json --version 8.0.0参数说明--version后面跟的是包版本号不写会默认拉最新版。生产项目建议锁定版本避免某天自动升级后 API 行为变了导致线上事故。JsonDemo.rar 里的示例代码如果引用了某个特定版本才有的方法版本对不上会直接报MissingMethodException。2.3 两种库的核心 API 对照选型不能只看性能还要看 API 顺手程度。我把最常用的几个操作列出来对比操作Newtonsoft.JsonSystem.Text.Json序列化JsonConvert.SerializeObject(obj)JsonSerializer.Serialize(obj)反序列化JsonConvert.DeserializeObjectT(json)JsonSerializer.DeserializeT(json)动态解析JObject.Parse(json)JsonDocument.Parse(json)忽略 nullNullValueHandling.IgnoreDefaultIgnoreCondition WhenWritingNull日期格式DateFormatStringJsonSerializerOptions里配 Converter从表里能看出来Newtonsoft.Json的 API 更直观尤其是动态解析那块JObject用起来像操作字典一样顺手。System.Text.Json性能确实好官方基准测试里序列化速度快不少但它的JsonDocument是只读的想改结构得用JsonNode而且默认对大小写敏感字段名对不上直接给你反序列化成 null这个坑后面会细说。我个人的习惯是新项目、纯 API 对接、对性能有要求用System.Text.Json老项目维护、需要频繁动态改 JSON 结构、团队更熟悉 Newtonsoft就继续用Newtonsoft.Json。JsonDemo.rar 里两套都有你可以对照着跑感受一下差异。3. 序列化与反序列化的核心操作从对象到 JSON 再回来3.1 定义一个能安全序列化的模型类先看最基础的场景把一个 C# 对象转成 JSON 字符串。假设我们有个用户信息类public class UserInfo { public int Id { get; set; } public string UserName { get; set; } public string Email { get; set; } public DateTime RegisterTime { get; set; } public Liststring Tags { get; set; } }这里有几个细节要注意。属性必须是public的且带get和set否则序列化时会被跳过。字段field默认不序列化除非你显式标记。Liststring这种集合类型没问题但如果是Dictionaryint, stringNewtonsoft 和 System.Text.Json 对 key 的处理方式不一样前者支持非字符串 key后者在默认配置下会报错。var user new UserInfo { Id 1001, UserName A同学, Email aexample.com, RegisterTime DateTime.Now, Tags new Liststring { vip, active } }; // Newtonsoft.Json 写法 string json1 JsonConvert.SerializeObject(user, Formatting.Indented); // System.Text.Json 写法 var options new JsonSerializerOptions { WriteIndented true }; string json2 JsonSerializer.Serialize(user, options);Formatting.Indented和WriteIndented true都是让输出带缩进方便人看。生产环境如果追求体积小可以去掉能省不少字节。JsonDemo.rar 里两种写法都有示例跑一遍就能看到输出差异。3.2 反序列化时字段名对不上的三种解法这是最高频的翻车点。接口返回的 JSON 字段是user_name你类里写的是UserName直接反序列化UserName就是 null。三种解法第一种用特性标注映射关系public class UserInfo { [JsonProperty(user_name)] // Newtonsoft public string UserName { get; set; } }public class UserInfo { [JsonPropertyName(user_name)] // System.Text.Json public string UserName { get; set; } }第二种全局配置命名策略。Newtonsoft 用SnakeCaseNamingStrategySystem.Text.Json 用JsonNamingPolicy.SnakeCaseLower.NET 8 开始内置。// Newtonsoft 全局蛇形命名 var settings new JsonSerializerSettings { ContractResolver new DefaultContractResolver { NamingStrategy new SnakeCaseNamingStrategy() } }; var user JsonConvert.DeserializeObjectUserInfo(json, settings);// System.Text.Json 全局蛇形命名.NET 8 var options new JsonSerializerOptions { PropertyNamingPolicy JsonNamingPolicy.SnakeCaseLower }; var user JsonSerializer.DeserializeUserInfo(json, options);第三种如果只是临时解析几个字段不想建类用动态方式// Newtonsoft 动态解析 var obj JObject.Parse(json); string name obj[user_name]?.ToString(); // System.Text.Json 动态解析 using var doc JsonDocument.Parse(json); string name2 doc.RootElement.GetProperty(user_name).GetString();三种方式各有适用场景。类结构稳定、字段多用特性或全局策略只是取一两个值动态解析更省事。JsonDemo.rar 里对这三种都有对应示例文件建议都跑一遍心里有个谱。3.3 日期、枚举、null 值的处理参数日期格式是另一个高频坑。C# 的DateTime序列化出来默认是 ISO 8601 带时区的格式但有些接口要求yyyy-MM-dd HH:mm:ss不带 T 不带 Z。Newtonsoft 可以这样配var settings new JsonSerializerSettings { DateFormatString yyyy-MM-dd HH:mm:ss, NullValueHandling NullValueHandling.Ignore, DefaultValueHandling DefaultValueHandling.Ignore };System.Text.Json 没有直接的DateFormatString需要自定义 Converterpublic class CustomDateTimeConverter : JsonConverterDateTime { public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) DateTime.Parse(reader.GetString()); public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) writer.WriteStringValue(value.ToString(yyyy-MM-dd HH:mm:ss)); }枚举默认序列化成数字想输出字符串名字Newtonsoft 加StringEnumConverterSystem.Text.Json 加JsonStringEnumConverter。null 值默认会输出field: null如果接口方不接受 null 字段就得配忽略策略。这些参数在 JsonDemo.rar 的配置示例里都能找到改一改就能用。4. 动态解析与配置文件读写不建类也能玩转 JSON4.1 JObject 和 JsonNode 的增删改查不是所有 JSON 都值得建一个类。比如读配置文件、处理结构不固定的第三方回调动态解析更灵活。Newtonsoft 的JObject用起来像操作嵌套字典var root JObject.Parse(json); // 读 string city root[address]?[city]?.ToString(); // 改 root[address][city] 某市; // 增 root[newField] newValue; // 删 root[obsolete]?.Remove(); // 转回字符串 string updated root.ToString(Formatting.Indented);System.Text.Json 在 .NET 6 之后提供了JsonNode用法类似但类型更严格var node JsonNode.Parse(json); string city node[address]?[city]?.GetValuestring(); node[address][city] 某市; node[newField] newValue; node[obsolete]?.AsObject().Remove(); string updated node.ToJsonString(new JsonSerializerOptions { WriteIndented true });注意JsonNode的索引返回的是JsonNode取值要调GetValueT()直接转 string 会编译不过。这个设计比 Newtonsoft 啰嗦一点但类型安全更好不容易出现运行时转换异常。4.2 用 JSON 做应用配置的读写封装很多小工具不需要数据库配置直接存 JSON 文件。我一般会封装一个简单的配置管理器public class AppConfig { public string ApiBaseUrl { get; set; } public int TimeoutSeconds { get; set; } public Dictionarystring, string Headers { get; set; } } public static class ConfigManager { private static readonly string ConfigPath config.json; public static AppConfig Load() { if (!File.Exists(ConfigPath)) return new AppConfig { TimeoutSeconds 30, Headers new Dictionarystring, string() }; string json File.ReadAllText(ConfigPath); return JsonSerializer.DeserializeAppConfig(json) ?? new AppConfig(); } public static void Save(AppConfig config) { var options new JsonSerializerOptions { WriteIndented true }; string json JsonSerializer.Serialize(config, options); File.WriteAllText(ConfigPath, json); } }这段代码的关键点读之前先判断文件是否存在不存在返回默认配置避免第一次运行直接抛异常。Deserialize可能返回 null用??兜底。写的时候带缩进方便手动改。JsonDemo.rar 里有类似的配置读写示例可以直接拿去改成自己需要的字段。4.3 大 JSON 文件的流式读取如果 JSON 文件几十兆甚至上百兆一次性ReadAllText再Parse会吃掉大量内存严重时直接 OOM。这时候要用流式读取。Newtonsoft 提供JsonTextReaderSystem.Text.Json 提供Utf8JsonReaderusing var stream File.OpenRead(bigdata.json); using var reader new StreamReader(stream); using var jsonReader new JsonTextReader(reader); while (jsonReader.Read()) { if (jsonReader.TokenType JsonToken.PropertyName jsonReader.Value?.ToString() targetField) { jsonReader.Read(); Console.WriteLine(jsonReader.Value); } }流式读取的核心思路是不把整个文档加载进内存而是逐个 token 扫描遇到目标字段就取值。代价是代码复杂不能随机访问。JsonDemo.rar 里如果包含大文件示例建议重点看这一块普通小文件用不上但数据量上来之后这是保命技能。5. 避坑与排查C# JSON 处理中最容易翻车的五个点5.1 反序列化后字段全是 null现象JSON 字符串明明有值反序列化出来的对象属性全是 null。原因字段名大小写不匹配或者命名策略没配对。System.Text.Json 默认大小写敏感UserName和userName在它眼里是两个不同的东西。解决要么给属性加[JsonPropertyName]特性要么全局配PropertyNameCaseInsensitive true。Newtonsoft 默认大小写不敏感但如果你手动改了ContractResolver也可能出现这个问题。5.2 循环引用导致序列化直接崩现象两个对象互相引用序列化时抛JsonSerializationException提示检测到循环引用。原因A 对象里有 BB 对象里又引用 A序列化器无限递归。解决Newtonsoft 配ReferenceLoopHandling.IgnoreSystem.Text.Json 配ReferenceHandler.IgnoreCycles。更好的做法是从模型设计上避免双向导航属性或者用 DTO 裁剪掉不需要的引用。5.3 日期时间少了几小时现象序列化出来的时间比实际时间少了 8 小时或者反序列化后时间对不上。原因时区处理不一致。DateTime.Now是本地时间DateTime.UtcNow是 UTC 时间序列化时如果带上了时区偏移对方按 UTC 解析就会差几个小时。解决统一用DateTimeOffset或者统一用 UTC 时间序列化展示时再转本地。JsonDemo.rar 里如果有日期示例注意看它用的是哪种。5.4 枚举值对不上号现象接口返回的枚举是字符串Active你类里定义的是int枚举反序列化直接报错。原因枚举的序列化格式不匹配。默认情况下 Newtonsoft 和 System.Text.Json 都把枚举当数字处理。解决加StringEnumConverter或JsonStringEnumConverter。如果字符串值和枚举名不完全一致还得自定义 Converter 做映射。5.5 大数字精度丢失现象JSON 里有个long类型的 ID反序列化后末几位变成了 0。原因JavaScript 的Number精度限制但 C# 本身没这个问题。出现这种情况通常是中间经过了 JS 层或者用了double去接long。解决C# 侧用long或decimal接不要用double。如果 JSON 里的数字超过了long范围用BigInteger或者当字符串处理。JsonDemo.rar 里如果有 ID 字段示例注意看它的类型定义。6. 进阶技巧自定义 Converter 和性能调优的实战参数6.1 写一个能处理复杂类型的自定义 Converter有些类型系统自带 Converter 搞不定比如IPAddress、Version、或者你自己定义的强类型 ID。这时候得自己写。以System.Text.Json为例public class VersionConverter : JsonConverterVersion { public override Version Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { string value reader.GetString(); return string.IsNullOrEmpty(value) ? null : new Version(value); } public override void Write(Utf8JsonWriter writer, Version value, JsonSerializerOptions options) { writer.WriteStringValue(value?.ToString()); } } // 注册 var options new JsonSerializerOptions(); options.Converters.Add(new VersionConverter());关键点Read里要处理 null 和空字符串否则遇到version: null会直接抛异常。Write里也要判空。注册方式可以是全局options.Converters.Add也可以是给单个属性加[JsonConverter(typeof(VersionConverter))]。JsonDemo.rar 里如果有自定义 Converter 示例重点看它怎么处理边界值。6.2 性能调优几个能省内存的参数如果 JSON 处理量大这几个参数值得调参数作用建议值DefaultBufferSize流式读写缓冲区大小16384 或 32768WriteIndented是否缩进生产环境 falseDefaultIgnoreCondition忽略 null 或默认值WhenWritingNullPropertyNameCaseInsensitive大小写不敏感按接口实际情况NumberHandling数字处理策略按需默认即可DefaultBufferSize默认是 16KB如果单条 JSON 特别大适当调大能减少扩容次数。WriteIndented在生产环境一定要关缩进带来的空白字符能占不少体积。DefaultIgnoreCondition设成WhenWritingNull能省掉大量field: null。6.3 用 Source Generator 减少反射开销.NET 7 之后System.Text.Json支持 Source Generator能在编译期生成序列化代码避免运行时反射启动速度和内存都有改善。用法是定义一个JsonSerializerContext[JsonSerializable(typeof(UserInfo))] [JsonSerializable(typeof(AppConfig))] internal partial class MyJsonContext : JsonSerializerContext { }然后序列化时用JsonSerializer.Serialize(user, MyJsonContext.Default.UserInfo)。这样生成的代码是强类型的没有反射AOT 场景下也能用。代价是每加一个类型都要在 Context 里注册类型多了维护成本会上来。JsonDemo.rar 里如果包含 Source Generator 示例建议在 .NET 7 项目里跑低版本不支持。6.4 一个我常用的验证习惯每次改完 JSON 相关的配置或模型我不会只看编译过不过而是会跑一个最小验证拿一条真实数据序列化再反序列化对比关键字段是否一致。这个习惯帮我拦下过好几次字段名写错、日期格式配错的问题。具体做法很简单var original new UserInfo { Id 1, UserName test, RegisterTime DateTime.Now }; string json JsonSerializer.Serialize(original); var restored JsonSerializer.DeserializeUserInfo(json); if (restored.UserName ! original.UserName) throw new Exception(字段名映射有问题);从那以后我每次动 JSON 配置都强制走一遍这个往返验证比事后在日志里大海捞针强得多。希望帮到你。本文还有配套的精品资源点击获取