简介:JsonDemo.rar 是一份面向 C# 开发者、尤其是初涉 .NET 数据交互场景的学习者准备的 JSON 处理示例工程,围绕 Newtonsoft.Json(Json.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.6+ | Newtonsoft.Json 或 System.Text.Json | 后者需通过 NuGet 安装 |
| .NET Core 3.1 / .NET 5+ | System.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.Json | System.Text.Json |
|---|---|---|
| 序列化 | JsonConvert.SerializeObject(obj) | JsonSerializer.Serialize(obj) |
| 反序列化 | JsonConvert.DeserializeObject<T>(json) | JsonSerializer.Deserialize<T>(json) |
| 动态解析 | JObject.Parse(json) | JsonDocument.Parse(json) |
| 忽略 null | NullValueHandling.Ignore | DefaultIgnoreCondition = WhenWritingNull |
| 日期格式 | DateFormatString | JsonSerializerOptions里配 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 List<string> Tags { get; set; } }这里有几个细节要注意。属性必须是public的,且带get和set,否则序列化时会被跳过。字段(field)默认不序列化,除非你显式标记。List<string>这种集合类型没问题,但如果是Dictionary<int, string>,Newtonsoft 和 System.Text.Json 对 key 的处理方式不一样,前者支持非字符串 key,后者在默认配置下会报错。
var user = new UserInfo { Id = 1001, UserName = "A同学", Email = "a@example.com", RegisterTime = DateTime.Now, Tags = new List<string> { "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 用SnakeCaseNamingStrategy,System.Text.Json 用JsonNamingPolicy.SnakeCaseLower(.NET 8 开始内置)。
// Newtonsoft 全局蛇形命名 var settings = new JsonSerializerSettings { ContractResolver = new DefaultContractResolver { NamingStrategy = new SnakeCaseNamingStrategy() } }; var user = JsonConvert.DeserializeObject<UserInfo>(json, settings);// System.Text.Json 全局蛇形命名(.NET 8+) var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower }; var user = JsonSerializer.Deserialize<UserInfo>(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,需要自定义 Converter:
public class CustomDateTimeConverter : JsonConverter<DateTime> { 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 加StringEnumConverter,System.Text.Json 加JsonStringEnumConverter。null 值默认会输出"field": null,如果接口方不接受 null 字段,就得配忽略策略。这些参数在 JsonDemo.rar 的配置示例里都能找到,改一改就能用。
4. 动态解析与配置文件读写:不建类也能玩转 JSON
4.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"]?.GetValue<string>(); node["address"]["city"] = "某市"; node["newField"] = "newValue"; node["obsolete"]?.AsObject().Remove(); string updated = node.ToJsonString(new JsonSerializerOptions { WriteIndented = true });注意JsonNode的索引返回的是JsonNode,取值要调GetValue<T>(),直接转 string 会编译不过。这个设计比 Newtonsoft 啰嗦一点,但类型安全更好,不容易出现运行时转换异常。
4.2 用 JSON 做应用配置的读写封装
很多小工具不需要数据库,配置直接存 JSON 文件。我一般会封装一个简单的配置管理器:
public class AppConfig { public string ApiBaseUrl { get; set; } public int TimeoutSeconds { get; set; } public Dictionary<string, 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 Dictionary<string, string>() }; string json = File.ReadAllText(ConfigPath); return JsonSerializer.Deserialize<AppConfig>(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 提供JsonTextReader,System.Text.Json 提供Utf8JsonReader:
using 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 对象里有 B,B 对象里又引用 A,序列化器无限递归。
解决:Newtonsoft 配ReferenceLoopHandling.Ignore,System.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 : JsonConverter<Version> { 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 或 32768 |
WriteIndented | 是否缩进 | 生产环境 false |
DefaultIgnoreCondition | 忽略 null 或默认值 | WhenWritingNull |
PropertyNameCaseInsensitive | 大小写不敏感 | 按接口实际情况 |
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.Deserialize<UserInfo>(json); if (restored.UserName != original.UserName) throw new Exception("字段名映射有问题");从那以后我每次动 JSON 配置,都强制走一遍这个往返验证,比事后在日志里大海捞针强得多。希望帮到你。
本文还有配套的精品资源,点击获取