1. 项目概述:从数据泥潭到清晰结构
在C#开发里,尤其是做Web API、桌面应用数据交换或者配置文件处理时,JSON几乎是无处不在的。你可能经常遇到这样的场景:从某个接口拿到一串长得让人眼花的文本,里面层层嵌套着各种括号和引号,你需要从中精准地提取出用户的名字或者订单的价格;又或者,你手头有一个Customer对象,里面有几十个属性,你需要把它打包成一个整洁的字符串,发送给另一个系统。这个过程,就是JSON的解析与生成。听起来简单,不就是用个Newtonsoft.Json或者System.Text.Json吗?但实际踩过坑的都知道,日期格式怎么处理、循环引用怎么避免、超大JSON怎么高效读取、属性名怎么按驼峰命名输出,每一个细节都可能让你调试半天。这篇文章,我就结合自己这些年处理各种“奇葩”JSON格式的经验,把C#里玩转JSON的那些核心技巧、隐藏陷阱和性能优化点,给你掰开揉碎了讲清楚。无论你是刚接触C#的新手,还是想优化现有代码的老手,这里都有你能直接“抄作业”的解决方案。
2. 核心工具选型:Newtonsoft.Json 与 System.Text.Json 的深度对比
十年前,C#处理JSON几乎只有Newtonsoft.Json(也就是Json.NET)这一个选择,它强大、灵活,社区生态极好。但后来,微软官方推出了System.Text.Json,旨在提供更高性能、更安全的默认方案。现在问题来了,我们该怎么选?这不是一个非此即彼的问题,而是一个基于场景的权衡。
2.1 性能与内存效率:System.Text.Json 的压倒性优势
如果你的应用对性能极其敏感,比如高频微服务接口、实时数据处理流水线,那么System.Text.Json通常是首选。它的底层采用了Span<T>和Utf8等现代.NET特性,在序列化和反序列化时,尤其是在处理大量小对象或大型文档时,其速度和内存效率显著优于Newtonsoft.Json。
举个例子,我们序列化一个包含10000个简单对象的列表:
// 使用 System.Text.Json var options = new JsonSerializerOptions { WriteIndented = false }; byte[] utf8Json = JsonSerializer.SerializeToUtf8Bytes(largeList, options); // 使用 Newtonsoft.Json string jsonString = JsonConvert.SerializeObject(largeList, Formatting.None);在基准测试中,JsonSerializer.SerializeToUtf8Bytes不仅速度更快,而且直接生成UTF-8字节数组,避免了从UTF-16字符串到UTF-8的额外转换,对于网络传输尤其友好。反序列化时,JsonSerializer.Deserialize同样利用了Utf8JsonReader这个高性能、低分配的阅读器。
注意:
System.Text.Json默认是大小写敏感的,而Newtonsoft.Json默认是大小写不敏感的。如果你从Newtonsoft迁移过来,接口突然报“反序列化失败”,首先就该检查属性名大小写匹配问题。可以通过JsonSerializerOptions.PropertyNameCaseInsensitive = true来设置不敏感。
2.2 功能与灵活性:Newtonsoft.Json 的丰富生态
然而,Newtonsoft.Json在功能丰富度和灵活性上目前依然领先。许多复杂的、非标准的场景,在System.Text.Json中可能需要更多代码才能实现,而在Newtonsoft.Json里可能一个属性标签就搞定了。
一些典型的、Newtonsoft.Json更易处理的场景包括:
- 多态类型序列化:处理继承类的集合时,
Newtonsoft.Json的TypeNameHandling设置可以轻松地在JSON中嵌入类型信息。 - 更强大的自定义转换器(Converter):虽然两者都支持转换器,但
Newtonsoft.Json的JsonConverter写起来更直观,社区有海量现成的转换器用于处理特殊格式(如特殊日期、自定义枚举、DataTable等)。 - 对动态类型(
dynamic、JObject、JArray)的无与伦比的支持:如果你需要处理结构不确定的JSON,Newtonsoft.Json提供的JToken体系(JObject,JArray,JValue)操作起来就像在JavaScript中一样灵活。 - 更精细的序列化控制:比如
DefaultValueHandling(忽略默认值)、NullValueHandling(处理空值)等,配置项非常详尽。
2.3 实战选型建议
我的经验是:
- 新建项目,尤其是ASP.NET Core Web API项目:优先使用
System.Text.Json。它是.NET Core及以后版本的默认库,与框架集成度最高(如模型绑定、响应输出默认都用它),性能好,且能满足绝大多数标准RESTful API的需求。 - 遗留项目或需要处理复杂、不规则JSON:继续使用
Newtonsoft.Json。迁移成本可能很高,且其强大功能在复杂场景下不可或缺。在ASP.NET Core中也可以通过安装Microsoft.AspNetCore.Mvc.NewtonsoftJson包并配置服务来继续使用它。 - 高性能中间件或库:强烈推荐
System.Text.Json,特别是利用其底层API(Utf8JsonWriter,Utf8JsonReader)进行流式处理,可以做到极致的内存控制。
3. 核心操作解析:序列化与反序列化的实战细节
选好了工具,我们来深入最核心的两个操作:把对象变成字符串(序列化/生成),以及把字符串变回对象(反序列化/解析)。
3.1 序列化:将对象转换为JSON字符串
序列化的目标是将一个C#对象图转换成一个符合JSON格式的字符串。这里的关键在于控制输出的格式。
使用 System.Text.Json:
public class Product { public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } public DateTime CreatedDate { get; set; } } var product = new Product { Id = 1, Name = "Laptop", Price = 999.99m, CreatedDate = DateTime.UtcNow }; // 基本序列化 string jsonString = JsonSerializer.Serialize(product); // 输出:{"Id":1,"Name":"Laptop","Price":999.99,"CreatedDate":"2023-10-27T06:30:00Z"} // 带格式化的序列化(美化输出,常用于调试) var options = new JsonSerializerOptions { WriteIndented = true }; string prettyJson = JsonSerializer.Serialize(product, options); // 输出带缩进和换行的JSON // 自定义属性命名策略(如驼峰命名) options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; string camelCaseJson = JsonSerializer.Serialize(product, options); // 输出:{"id":1,"name":"Laptop","price":999.99,"createdDate":"2023-10-27T06:30:00Z"}使用 Newtonsoft.Json:
string jsonString = JsonConvert.SerializeObject(product, Formatting.Indented); // 同样可以美化输出 var settings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver(), DateFormatString = "yyyy-MM-dd" // 自定义日期格式 }; string customJson = JsonConvert.SerializeObject(product, Formatting.Indented, settings);实操心得:在Web API开发中,为了与前端JavaScript社区习惯保持一致,强烈建议统一使用驼峰命名(Camel Case)作为JSON的属性名规范。这能减少前后端沟通成本。在
System.Text.Json中设置JsonNamingPolicy.CamelCase,在Newtonsoft.Json中使用CamelCasePropertyNamesContractResolver即可。
3.2 反序列化:将JSON字符串转换为对象
反序列化是解析的核心,你需要确保JSON字符串的结构与你目标对象的类型定义相匹配。
使用 System.Text.Json:
string json = @"{""id"":1,""name"":""Laptop"",""price"":999.99}"; // 基本反序列化 - 注意属性名大小写! var product = JsonSerializer.Deserialize<Product>(json); // 如果json是驼峰,而Product类属性是帕斯卡命名,这里会失败,除非设置PropertyNameCaseInsensitive = true var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true // 忽略大小写 }; product = JsonSerializer.Deserialize<Product>(json, options); // 成功 // 处理不完整的JSON(JSON只包含对象的部分属性) string partialJson = @"{""id"":1,""name"":""Laptop""}"; product = JsonSerializer.Deserialize<Product>(partialJson, options); // product.Price将为默认值0,不会报错使用 Newtonsoft.Json:
var product = JsonConvert.DeserializeObject<Product>(json); // Newtonsoft.Json 默认就是大小写不敏感的,所以上面的json能直接解析到Product类的Id、Name属性。 // 更灵活地处理未知属性 var settings = new JsonSerializerSettings { MissingMemberHandling = MissingMemberHandling.Error // 如果JSON中有属性在类中不存在,则抛出异常 }; try { product = JsonConvert.DeserializeObject<Product>(json, settings); } catch (JsonSerializationException ex) { Console.WriteLine($"发现了未知属性: {ex.Message}"); }处理嵌套对象和集合:JSON经常包含数组和嵌套对象,反序列化时它们会自然地映射到C#的列表和类属性上。
public class Order { public int OrderId { get; set; } public List<Product> Items { get; set; } // 对应JSON数组 public Customer Buyer { get; set; } // 对应嵌套的JSON对象 } string complexJson = @"{ ""orderId"": 1001, ""items"": [ {""id"":1, ""name"":""Mouse"", ""price"":25.99}, {""id"":2, ""name"":""Keyboard"", ""price"":79.99} ], ""buyer"": { ""customerId"": 500, ""fullName"": ""John Doe"" } }"; var order = JsonSerializer.Deserialize<Order>(complexJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); Console.WriteLine(order.Items.Count); // 输出: 2 Console.WriteLine(order.Buyer.FullName); // 输出: John Doe4. 高级特性与疑难杂症处理
掌握了基础,我们来看看那些让人头疼的高级场景和常见坑点。
4.1 自定义序列化:用特性(Attribute)和转换器(Converter)控制一切
有时默认的序列化行为不符合你的需求。比如,你想忽略某个属性、自定义日期格式、或者把一个枚举序列化成字符串而不是数字。
使用特性(更简单直观):
using System.Text.Json.Serialization; // 注意命名空间 public class Product { public int Id { get; set; } [JsonPropertyName("product_name")] // 自定义JSON中的属性名 public string Name { get; set; } [JsonIgnore] // 完全忽略此属性,不参与序列化和反序列化 public string InternalCode { get; set; } [JsonConverter(typeof(JsonStringEnumConverter))] // 将枚举序列化为字符串 public ProductCategory Category { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] // 仅当值为默认值时忽略 public decimal? Discount { get; set; } } public enum ProductCategory { Electronics, Books, Clothing }Newtonsoft.Json也有类似的特性,如[JsonProperty]、[JsonIgnore]。
编写自定义转换器(处理复杂场景):当内置特性无法满足时,就需要自定义转换器。例如,处理一个特殊的日期字符串格式"dd/MM/yyyy"。
System.Text.Json 自定义转换器示例:
public class CustomDateTimeConverter : JsonConverter<DateTime> { private readonly string _format; public CustomDateTimeConverter(string format) => _format = format; public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { // 从JSON中读取字符串,并按指定格式解析为DateTime var dateString = reader.GetString(); return DateTime.ParseExact(dateString, _format, CultureInfo.InvariantCulture); } public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) { // 将DateTime按指定格式写入JSON字符串 writer.WriteStringValue(value.ToString(_format, CultureInfo.InvariantCulture)); } } // 使用方式1:通过特性标注在属性上 public class Event { [JsonConverter(typeof(CustomDateTimeConverter), "dd/MM/yyyy")] public DateTime EventDate { get; set; } } // 使用方式2:在JsonSerializerOptions中全局注册 var options = new JsonSerializerOptions(); options.Converters.Add(new CustomDateTimeConverter("dd/MM/yyyy"));4.2 处理循环引用与引用相等性
这是序列化对象图时的一个经典问题。比如,Order对象包含一个Customer,而这个Customer对象又有一个Orders列表指向包含它的Order,这就构成了循环引用。默认序列化器会陷入无限循环导致栈溢出。
Newtonsoft.Json 的解决方案:
var settings = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore // 忽略循环引用,在引用处输出null // 或者 ReferenceLoopHandling = ReferenceLoopHandling.Serialize,配合 PreserveReferencesHandling }; var json = JsonConvert.SerializeObject(orderWithCycle, Formatting.Indented, settings);System.Text.Json 的解决方案:System.Text.Json默认不支持循环引用,且没有内置的ReferenceLoopHandling。这是其设计上为了性能和安全性做的取舍。如果你的数据结构确实存在循环引用,你有几个选择:
- 重新设计数据结构:这是最根本的解决方案,考虑使用DTO(数据传输对象)在序列化时打破循环,只传递必要的数据。
- 使用
[JsonIgnore]特性:在构成循环的某个属性上标记[JsonIgnore],手动切断循环。 - 考虑换用
Newtonsoft.Json:如果数据结构复杂且无法更改,在ASP.NET Core中集成Newtonsoft.Json可能是更务实的选择。
4.3 动态JSON与匿名类型处理
当你面对结构未知或变化的JSON时,强类型反序列化就不好用了。这时需要动态解析。
使用 System.Text.Json 的 JsonDocument 和 JsonElement:这是高性能、低分配的选择,适合只读访问。
string jsonString = "{\"name\": \"John\", \"age\": 30, \"hobbies\": [\"reading\", \"gaming\"]}"; using JsonDocument doc = JsonDocument.Parse(jsonString); JsonElement root = doc.RootElement; string name = root.GetProperty("name").GetString(); int age = root.GetProperty("age").GetInt32(); JsonElement hobbies = root.GetProperty("hobbies"); foreach (JsonElement hobby in hobbies.EnumerateArray()) { Console.WriteLine(hobby.GetString()); } // 注意:JsonDocument是Disposable的,使用using语句管理生命周期。使用 Newtonsoft.Json 的 JToken 体系:功能更强大,支持动态修改,语法更接近JavaScript。
string jsonString = "..."; JObject jObject = JObject.Parse(jsonString); string name = (string)jObject["name"]; int age = (int)jObject["age"]; // 动态访问,即使属性不存在也不会立即报错 var maybeValue = jObject["address"]?["city"]?.ToString(); // 动态修改和创建 jObject["newField"] = "new value"; JArray newArray = new JArray { "item1", "item2" }; jObject.Add("dynamicArray", newArray); string modifiedJson = jObject.ToString();反序列化到匿名类型或字典:对于结构已知但不想创建完整类的情况,可以反序列化到dynamic(Newtonsoft)或匿名类型、字典。
// System.Text.Json 反序列化到字典 var dict = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonString); var name = dict["name"].ToString(); // Newtonsoft.Json 反序列化到 dynamic dynamic dynamicObj = JsonConvert.DeserializeObject(jsonString); Console.WriteLine(dynamicObj.name);4.4 性能优化与异步流处理
处理几MB甚至几百MB的JSON文件时,性能至关重要。
使用流式API(System.Text.Json):避免一次性将整个JSON字符串加载到内存。
// 异步从文件流反序列化 await using FileStream fs = File.OpenRead("largefile.json"); var data = await JsonSerializer.DeserializeAsync<List<MyData>>(fs, options); // 异步序列化到文件流 await using FileStream writeFs = File.CreateWrite("output.json"); await JsonSerializer.SerializeAsync(writeFs, largeDataList, options);使用 Utf8JsonReader/Utf8JsonWriter 进行手动、极致的控制:这是最高性能、最低内存分配的方式,但代码最复杂。适合编写自己的高性能解析器或处理非标准JSON。
// 这是一个简化示例,手动读取一个JSON数组中的某个特定字段 ReadOnlySpan<byte> jsonUtf8 = Encoding.UTF8.GetBytes(jsonString); // 实际中可能来自网络流 var reader = new Utf8JsonReader(jsonUtf8); while (reader.Read()) { if (reader.TokenType == JsonTokenType.PropertyName && reader.ValueTextEquals("targetField")) { reader.Read(); // 移动到属性值 if (reader.TokenType == JsonTokenType.String) { string value = reader.GetString(); // 处理找到的值 } } }5. 常见问题排查与调试技巧
即使掌握了所有API,在实际开发中你还是会遇到各种奇怪的问题。这里记录一些最常见的坑和排查方法。
5.1 反序列化失败:原因分析与快速定位
当Deserialize抛出JsonException时,别慌,按以下步骤排查:
| 错误现象 | 可能原因 | 排查方法 |
|---|---|---|
JsonException: The JSON value could not be converted to... | 类型不匹配。例如JSON中是字符串"123",但C#属性是int。 | 1. 检查JSON和C#类的属性类型是否一致。 2. 使用 JsonElement的GetRawText()或ToString()查看实际解析到的值。3. 考虑使用可为空类型( int?)或自定义转换器。 |
JsonException: ‘$’ is an invalid start of a value. | JSON格式错误,如缺少引号、括号不匹配、存在非法字符。 | 1. 将JSON字符串粘贴到在线JSON校验器(如 jsonlint.com)检查语法。 2. 检查字符串中是否包含未转义的控制字符或换行符。 |
反序列化后属性为null或默认值 | 1. JSON中缺少该属性。 2. 属性名大小写不匹配( System.Text.Json默认敏感)。3. 属性有自定义setter且逻辑导致未赋值。 | 1. 确认JSON字符串是否包含该属性。 2. 设置 PropertyNameCaseInsensitive = true。3. 检查属性的set访问器内部逻辑。 |
| 循环引用导致栈溢出(Newtonsoft) | 对象图中存在循环引用。 | 配置ReferenceLoopHandling.Ignore。对于System.Text.Json,需重构模型或忽略属性。 |
调试利器:
- 使用
JsonDocument或JObject进行探索性解析:当不确定JSON结构时,先把它解析成JsonDocument或JObject,然后遍历其节点,打印出键和值的类型,这能帮你快速理解数据结构。 - 在Newtonsoft.Json中启用跟踪(TraceWriter):这在诊断复杂序列化问题时非常有用。
MemoryTraceWriter traceWriter = new MemoryTraceWriter(); var settings = new JsonSerializerSettings { TraceWriter = traceWriter }; var result = JsonConvert.DeserializeObject<MyClass>(json, settings); Console.WriteLine(traceWriter.ToString()); // 输出详细的序列化日志
5.2 日期与时间格式的永恒之战
日期时间是跨系统交互的常见痛点。JSON标准没有定义日期格式,通常使用ISO 8601字符串(如"2023-10-27T06:30:00Z")。
System.Text.Json:默认序列化为ISO 8601格式。你可以通过JsonSerializerOptions.Converters添加自定义的DateTimeConverter来改变格式。Newtonsoft.Json:默认也是ISO 8601,但可以通过DateFormatString设置轻松自定义,如"yyyy-MM-dd"。
重要提醒:在处理日期时,务必明确时区。最佳实践是,在业务逻辑内部统一使用
DateTime.UtcNow获取时间,序列化时使用UTC时间(带Z后缀),并在前端或消费者端根据需要进行时区转换。这可以避免因服务器所在地时区不同而导致的混乱。
5.3 处理特殊字符与编码
JSON字符串中的特殊字符,如引号"、反斜杠\、换行符\n等,需要进行转义。两个库都会自动处理这些转义。
但需要注意:如果你从一些非标准源(如拼接的字符串、某些旧系统)获取“JSON”,可能会遇到未转义的控制字符。这时,在反序列化前,你可能需要先进行字符串清洗或使用更宽松的解析模式(Newtonsoft.Json的JsonLoadSettings可以设置一些容错)。
关于编码,JSON标准规定使用UTF-8、UTF-16或UTF-32。System.Text.Json强烈推荐并优化了UTF-8的处理。当从文件或网络流中读取时,确保使用正确的编码。StreamReader默认使用UTF-8,这通常是安全的。
5.4 属性缺失与默认值处理
有时,JSON可能只包含对象的部分字段。反序列化时,缺失的属性会被设置为C#类型的默认值(如int为0,string为null,bool为false)。
- 如何区分“提供的null”和“缺失的属性”?在
System.Text.Json中,如果JSON中明确设置了null("property": null),并且你的C#属性是可为空的(如string?或int?),那么反序列化后该属性值为null。如果JSON中完全没出现这个属性,那么该属性保持其初始值(如果没在构造函数中初始化,则是默认值)。要严格区分,可能需要使用JsonElement进行手动解析或自定义转换器。 - 忽略默认值序列化:为了减少JSON体积,你可能不想序列化那些等于默认值的属性。
System.Text.Json:JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefaultNewtonsoft.Json:DefaultValueHandling = DefaultValueHandling.Ignore
6. 实战案例:构建一个健壮的配置读取器
让我们用一个综合案例把上面的知识点串起来。假设我们有一个应用的配置文件appsettings.json,结构相对复杂,并且我们需要在程序启动时将其读入一个强类型配置对象。
配置文件示例 (appsettings.json):
{ "AppName": "MyService", "Logging": { "Level": "Information", "FilePath": "/var/log/myservice.log", "RetentionDays": 30 }, "Features": { "EnableCache": true, "CacheDurationMinutes": 5 }, "Endpoints": [ { "Name": "Primary", "Url": "https://api.primary.example.com", "TimeoutSeconds": 30 }, { "Name": "Fallback", "Url": "https://api.fallback.example.com", "TimeoutSeconds": 60 } ] }对应的C#配置类:
public class AppConfig { public string AppName { get; set; } public LoggingConfig Logging { get; set; } public FeaturesConfig Features { get; set; } public List<EndpointConfig> Endpoints { get; set; } } public class LoggingConfig { public string Level { get; set; } public string FilePath { get; set; } public int RetentionDays { get; set; } } public class FeaturesConfig { public bool EnableCache { get; set; } public int CacheDurationMinutes { get; set; } } public class EndpointConfig { public string Name { get; set; } public string Url { get; set; } public int TimeoutSeconds { get; set; } }健壮的配置加载代码(使用 System.Text.Json):
using System.Text.Json; public class ConfigurationLoader { private readonly JsonSerializerOptions _options; public ConfigurationLoader() { _options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, // 忽略大小写,更健壮 ReadCommentHandling = JsonCommentHandling.Skip, // 跳过JSON中的注释 AllowTrailingCommas = true, // 允许末尾逗号 // 可以在这里添加自定义转换器,例如处理特殊日期格式 }; } public AppConfig LoadConfig(string filePath) { if (!File.Exists(filePath)) { throw new FileNotFoundException($"配置文件未找到: {filePath}"); } try { // 使用异步流式读取,适合大文件 using FileStream fileStream = File.OpenRead(filePath); var config = JsonSerializer.DeserializeAsync<AppConfig>(fileStream, _options).GetAwaiter().GetResult(); // 同步上下文中使用 // 基础验证 if (config == null) { throw new InvalidOperationException("反序列化配置对象为null。"); } if (string.IsNullOrEmpty(config.AppName)) { // 可以设置默认值,或抛出更具体的异常 config.AppName = "DefaultAppName"; // 或者: throw new InvalidDataException("AppName 是必须的配置项。"); } // 可以添加更多业务逻辑验证,如Endpoints列表不能为空等 if (config.Endpoints?.Any() != true) { config.Endpoints = new List<EndpointConfig> { new EndpointConfig { Name = "Default", Url = "http://localhost", TimeoutSeconds = 30 } }; } return config; } catch (JsonException jsonEx) { // 捕获JSON解析错误,包装成更有意义的异常 throw new InvalidOperationException($"配置文件 '{filePath}' 格式无效。详细信息: {jsonEx.Message}", jsonEx); } catch (Exception ex) when (ex is not FileNotFoundException) { // 处理其他异常 throw new InvalidOperationException($"加载配置文件 '{filePath}' 时发生未知错误。", ex); } } // 可选:提供一个动态查看配置的方法,用于调试 public string InspectConfig(string filePath) { using JsonDocument doc = JsonDocument.Parse(File.ReadAllText(filePath)); using var stream = new MemoryStream(); using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true }); doc.RootElement.WriteTo(writer); writer.Flush(); return Encoding.UTF8.GetString(stream.ToArray()); } }使用示例:
var loader = new ConfigurationLoader(); try { AppConfig config = loader.LoadConfig("appsettings.json"); Console.WriteLine($"应用名: {config.AppName}"); Console.WriteLine($"缓存是否启用: {config.Features.EnableCache}"); Console.WriteLine($"第一个端点: {config.Endpoints[0].Name} - {config.Endpoints[0].Url}"); } catch (Exception ex) { Console.WriteLine($"加载配置失败: {ex.Message}"); // 可以在这里加载默认配置或终止程序 }这个案例展示了如何将JSON解析融入到一个实际的、健壮的组件中。它包含了错误处理、默认值设置、配置验证以及使用JsonDocument进行调试的实用技巧。在实际项目中,ASP.NET Core的配置系统已经基于System.Text.Json或Newtonsoft.Json做了非常完善的封装(IConfiguration),但其底层原理与我们这里所探讨的完全一致。理解这些底层操作,能让你在需要自定义配置源或处理特殊格式时游刃有余。