C# JSON处理全攻略:Newtonsoft.Json与System.Text.Json深度对比与实战

📅 2026/7/31 5:39:34 👁️ 阅读次数 📝 编程学习
C# JSON处理全攻略:Newtonsoft.Json与System.Text.Json深度对比与实战

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.JsonTypeNameHandling设置可以轻松地在JSON中嵌入类型信息。
  • 更强大的自定义转换器(Converter):虽然两者都支持转换器,但Newtonsoft.JsonJsonConverter写起来更直观,社区有海量现成的转换器用于处理特殊格式(如特殊日期、自定义枚举、DataTable等)。
  • 对动态类型(dynamicJObjectJArray)的无与伦比的支持:如果你需要处理结构不确定的JSON,Newtonsoft.Json提供的JToken体系(JObject,JArray,JValue)操作起来就像在JavaScript中一样灵活。
  • 更精细的序列化控制:比如DefaultValueHandling(忽略默认值)、NullValueHandling(处理空值)等,配置项非常详尽。

2.3 实战选型建议

我的经验是:

  1. 新建项目,尤其是ASP.NET Core Web API项目:优先使用System.Text.Json。它是.NET Core及以后版本的默认库,与框架集成度最高(如模型绑定、响应输出默认都用它),性能好,且能满足绝大多数标准RESTful API的需求。
  2. 遗留项目或需要处理复杂、不规则JSON:继续使用Newtonsoft.Json。迁移成本可能很高,且其强大功能在复杂场景下不可或缺。在ASP.NET Core中也可以通过安装Microsoft.AspNetCore.Mvc.NewtonsoftJson包并配置服务来继续使用它。
  3. 高性能中间件或库:强烈推荐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 Doe

4. 高级特性与疑难杂症处理

掌握了基础,我们来看看那些让人头疼的高级场景和常见坑点。

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。这是其设计上为了性能和安全性做的取舍。如果你的数据结构确实存在循环引用,你有几个选择:

  1. 重新设计数据结构:这是最根本的解决方案,考虑使用DTO(数据传输对象)在序列化时打破循环,只传递必要的数据。
  2. 使用[JsonIgnore]特性:在构成循环的某个属性上标记[JsonIgnore],手动切断循环。
  3. 考虑换用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#属性是int1. 检查JSON和C#类的属性类型是否一致。
2. 使用JsonElementGetRawText()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,需重构模型或忽略属性。

调试利器:

  • 使用JsonDocumentJObject进行探索性解析:当不确定JSON结构时,先把它解析成JsonDocumentJObject,然后遍历其节点,打印出键和值的类型,这能帮你快速理解数据结构。
  • 在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.JsonJsonLoadSettings可以设置一些容错)。

关于编码,JSON标准规定使用UTF-8、UTF-16或UTF-32。System.Text.Json强烈推荐并优化了UTF-8的处理。当从文件或网络流中读取时,确保使用正确的编码。StreamReader默认使用UTF-8,这通常是安全的。

5.4 属性缺失与默认值处理

有时,JSON可能只包含对象的部分字段。反序列化时,缺失的属性会被设置为C#类型的默认值(如int为0,stringnullboolfalse)。

  • 如何区分“提供的null”和“缺失的属性”?System.Text.Json中,如果JSON中明确设置了null"property": null),并且你的C#属性是可为空的(如string?int?),那么反序列化后该属性值为null。如果JSON中完全没出现这个属性,那么该属性保持其初始值(如果没在构造函数中初始化,则是默认值)。要严格区分,可能需要使用JsonElement进行手动解析或自定义转换器。
  • 忽略默认值序列化:为了减少JSON体积,你可能不想序列化那些等于默认值的属性。
    • System.Text.Json:JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault
    • Newtonsoft.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.JsonNewtonsoft.Json做了非常完善的封装(IConfiguration),但其底层原理与我们这里所探讨的完全一致。理解这些底层操作,能让你在需要自定义配置源或处理特殊格式时游刃有余。