C# JSON解析实战:System.Text.Json与Newtonsoft.Json深度对比与性能优化

1. 从“字符串”到“对象”:为什么C#开发者必须掌握JSON解析

如果你用C#写过任何需要和外部系统打交道的程序,无论是调用一个Web API、读取一个配置文件,还是把数据存到NoSQL数据库里,那你肯定绕不开JSON。它就像程序员世界里的“普通话”,几乎成了数据交换的事实标准。但很多刚入门的C#开发者,面对一段JSON字符串,第一反应可能就是去网上搜“C# JSON解析”,然后复制一段用Newtonsoft.Json(也就是Json.NET)的代码。这没错,但如果你只停留在“能用”的层面,可能会在后续开发中踩不少坑。

我见过不少项目,因为JSON解析处理不当,导致接口性能低下、内存泄漏,甚至因为日期格式、枚举处理不一致而引发线上bug。JSON解析远不止是JsonConvert.DeserializeObject<T>(jsonString)这一行代码那么简单。它涉及到序列化器的选型、性能优化、复杂嵌套结构的处理、自定义转换,以及在异步流、Span等现代C#特性下的最佳实践。

这篇文章,我就以一个老C#码农的身份,结合我这些年踩过的坑和积累的经验,带你彻底搞懂在C#里解析JSON的方方面面。我们会从最基础的System.Text.JsonNewtonsoft.Json讲起,深入到性能敏感场景的优化、处理那些“不标准”的JSON,最后再聊聊在真实项目(比如你提到的上位机、SCPI编程、API调用)里如何优雅地集成JSON解析。目标很简单:让你不仅能解析JSON,更能“精通”解析,写出既健壮又高效的代码。

2. 核心武器库:System.Text.Json 与 Newtonsoft.Json 的深度对比与选型

当你要在C#中处理JSON时,首先面临的就是工具选择。现在主要有两大阵营:微软官方的System.Text.Json和经久不衰的第三方库Newtonsoft.Json(Json.NET)。很多人会问:“我该用哪个?” 这不是一个非此即彼的问题,而是一个需要根据场景权衡的选择。

2.1 System.Text.Json:为性能和现代C#而生

System.Text.Json是从.NET Core 3.0开始引入的,它被集成在.NET运行时库中,意味着你不需要安装额外的NuGet包。它的设计初衷就是高性能和低内存分配。

它的核心优势在于:

  • 性能卓越:底层大量使用Span<T>Utf8直接操作,避免了不必要的字符串编码转换和内存分配。在大量序列化/反序列化的场景下,性能提升非常明显,有时能达到Newtonsoft.Json的2倍甚至更多。
  • 内存效率高:支持异步流式读写(Utf8JsonReader,Utf8JsonWriter),可以处理GB级别的JSON文件而无需一次性加载到内存。
  • 安全性:默认配置更严格,有助于避免某些安全反序列化漏洞。

但它的“脾气”也更倔强一些:

  • 默认行为差异:属性名默认使用“驼峰命名”(CamelCase)进行匹配,而Newtonsoft.Json默认是“帕斯卡命名”(PascalCase)。这经常是迁移时第一个遇到的坑。
  • 功能“精简”:出于性能和安全性考虑,它没有实现Newtonsoft.Json的所有功能。例如,默认不支持循环引用、不支持非字符串字典键、某些高级的自定义转换器写起来更繁琐。

一个简单的反序列化例子,展示了默认命名策略的差异:

using System.Text.Json; public class DeviceReading { public string SensorId { get; set; } // PascalCase 属性名 public double Value { get; set; } } string json = @"{""sensorId"": ""temp_01"", ""value"": 25.5}"; // CamelCase JSON键 // System.Text.Json 默认需要大小写不敏感或显式配置才能匹配 var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true // 启用不区分大小写匹配 }; var reading = JsonSerializer.Deserialize<DeviceReading>(json, options); Console.WriteLine(reading.SensorId); // 输出:temp_01

2.2 Newtonsoft.Json:功能全面的“瑞士军刀”

Newtonsoft.Json历史悠久,社区庞大,几乎支持你能想到的所有JSON相关场景。它的API设计非常人性化,功能丰富。

它的强大之处在于:

  • 极高的灵活性:处理循环引用、多态类型序列化、忽略默认值、合并JSON等高级功能开箱即用。
  • 丰富的设置:通过JsonSerializerSettings可以精细控制序列化的每一个环节,满足各种奇葩的接口需求。
  • 卓越的容错性:对于格式不太规范的JSON(比如尾随逗号)有更好的容忍度。
  • 强大的自定义:写自定义JsonConverter相对直观,可以处理非常复杂的转换逻辑。

当然,代价也是有的:

  • 性能开销:为了提供灵活性,它在性能上做出了妥协,通常比System.Text.Json慢。
  • 外部依赖:需要从NuGet安装,增加了项目依赖。
using Newtonsoft.Json; // 同样的类和JSON var reading = JsonConvert.DeserializeObject<DeviceReading>(json); // 默认就能匹配,更宽松 Console.WriteLine(reading.SensorId); // 输出:temp_01 // 轻松处理复杂场景,如忽略空值 var settings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore }; string cleanJson = JsonConvert.SerializeObject(reading, settings);

2.3 实战选型指南:什么时候用什么?

根据我的经验,可以遵循以下原则:

  1. 新建项目,尤其是高性能、高吞吐量的服务(如Web API、微服务)优先选择System.Text.Json。利用其高性能和内置优势,从开始就建立良好的性能基线。
  2. 旧项目迁移或维护:如果项目严重依赖Newtonsoft.Json的高级特性,且迁移成本高,继续使用Newtonsoft.Json是更务实的选择。不要为了迁移而迁移。
  3. 需要处理非常复杂或“脏”的JSON数据:比如接口不规范、需要复杂自定义转换、处理多态类型集合。Newtonsoft.Json通常是更省力的工具
  4. 类库开发:如果你在开发一个供他人使用的类库,需要最大兼容性,并且不希望强制用户使用特定的JSON库,可以考虑抽象一层,或者同时支持两者(通过条件编译)。但通常,类库内部使用System.Text.Json是更现代的选择。

注意:在ASP.NET Core项目中,虽然控制器默认使用System.Text.Json,但你仍然可以通过安装Microsoft.AspNetCore.Mvc.NewtonsoftJson包并配置服务来换回Newtonsoft.Json。这通常是为了兼容旧有客户端或使用某些Newtonsoft.Json独占特性。

3. 超越基础API:高性能解析与流式处理实战

当你需要处理日志文件、大数据导出的JSON数组,或者从网络流中持续读取JSON消息时,一次性将整个JSON字符串反序列化成对象模型可能会耗尽内存。这时,就需要用到流式处理(Streaming)。

3.1 使用 System.Text.Json 进行流式读取

System.Text.Json提供了Utf8JsonReaderUtf8JsonWriter这两个底层结构体,用于高性能、低分配的流式读写。它们直接操作UTF-8编码的字节,效率极高。

假设我们有一个很大的JSON文件data.json,里面包含一个设备读数数组,我们想逐条处理,而不是全部加载到内存。

using System.Text.Json; async Task ProcessLargeJsonFileAsync(string filePath) { await using var fileStream = File.OpenRead(filePath); var buffer = new byte[4096]; // 缓冲区 // 读取直到遇到开始数组的令牌 '[' int bytesRead; while ((bytesRead = await fileStream.ReadAsync(buffer)) > 0) { // 这里简化了逻辑,实际需要更复杂的状态机来解析流中的JSON // 更常见的做法是使用 JsonDocument.ParseAsync 进行部分读取 } }

对于大多数场景,使用JsonDocumentJsonSerializer.DeserializeAsyncEnumerable是更友好的选择。

使用JsonDocument进行DOM式流解析:JsonDocument可以将JSON解析成一个只读的文档对象模型(DOM),它比反序列化成强类型对象更轻量,并且支持随机访问元素。它底层也使用了Utf8JsonReader

using System.Text.Json; async Task ProcessJsonDocumentAsync(string filePath) { await using var fileStream = File.OpenRead(filePath); // 异步解析整个流,但JsonDocument本身是轻量级的,不代表整个文件都在内存中展开为对象树 using JsonDocument document = await JsonDocument.ParseAsync(fileStream); JsonElement root = document.RootElement; // 假设根元素是一个数组 if (root.ValueKind == JsonValueKind.Array) { foreach (JsonElement element in root.EnumerateArray()) { // 逐条处理每个设备读数对象 if (element.TryGetProperty("sensorId", out JsonElement sensorIdProp) && element.TryGetProperty("value", out JsonElement valueProp)) { string sensorId = sensorIdProp.GetString(); double value = valueProp.GetDouble(); Console.WriteLine($"Sensor: {sensorId}, Value: {value}"); // 处理业务逻辑... } } } }

使用DeserializeAsyncEnumerable进行异步流反序列化(.NET 6+):这是处理大型JSON数组最优雅的方式。它一次只反序列化一个数组元素到内存中。

using System.Text.Json; public record DeviceReading(string SensorId, double Value); async IAsyncEnumerable<DeviceReading> StreamReadingsFromFileAsync(string filePath) { await using var fileStream = File.OpenRead(filePath); // 反序列化一个异步的DeviceReading流 var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; await foreach (var reading in JsonSerializer.DeserializeAsyncEnumerable<DeviceReading>(fileStream, options)) { if (reading != null) { yield return reading; // 每次yield一个对象,内存压力极小 } } } // 消费端 await foreach (var reading in StreamReadingsFromFileAsync("huge_data.json")) { // 处理每一条数据 await ProcessReadingAsync(reading); }

3.2 使用 Newtonsoft.Json 进行流式读取

Newtonsoft.Json同样支持流式处理,主要通过JsonTextReader类。

using Newtonsoft.Json; using (var fileStream = File.OpenText("large.json")) using (var jsonReader = new JsonTextReader(fileStream)) { var serializer = new JsonSerializer(); // 假设读取一个对象数组 if (jsonReader.Read() && jsonReader.TokenType == JsonToken.StartArray) { while (jsonReader.Read() && jsonReader.TokenType != JsonToken.EndArray) { // 反序列化数组中的单个对象 var reading = serializer.Deserialize<DeviceReading>(jsonReader); ProcessReading(reading); } } }

性能对比与选择:在流式处理场景下,System.Text.JsonUtf8JsonReaderDeserializeAsyncEnumerable在性能和内存效率上通常有显著优势,特别是处理UTF-8字节源时。Newtonsoft.Json的流式API虽然功能完备,但在极限性能场景下可能稍逊一筹。对于大多数应用,JsonDocumentDeserializeAsyncEnumerable已经能解决99%的大文件处理问题。

4. 驯服“不标准”的JSON:自定义转换与复杂结构处理

现实世界的JSON数据很少是教科书般的完美。你可能会遇到日期格式千奇百怪、枚举需要序列化成字符串、某些字段可能是多种类型之一(多态),或者JSON结构与你理想的C#模型不匹配。这就需要自定义转换器(Converter)出场了。

4.1 处理非标准日期格式

这是最常见的问题之一。API可能返回"2023-10-27""27/10/2023",甚至时间戳1698393600

在 System.Text.Json 中:你需要编写自定义的JsonConverter<T>

using System.Text.Json; using System.Text.Json.Serialization; public class CustomDateTimeConverter : JsonConverter<DateTime> { private readonly string _format; public CustomDateTimeConverter(string format = "yyyy-MM-dd") { _format = format; } public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType == JsonTokenType.String) { if (DateTime.TryParseExact(reader.GetString(), _format, null, System.Globalization.DateTimeStyles.None, out var date)) { return date; } } else if (reader.TokenType == JsonTokenType.Number) { // 处理Unix时间戳(秒) return DateTimeOffset.FromUnixTimeSeconds(reader.GetInt64()).UtcDateTime; } // 如果都不匹配,可以回退到默认解析,或者抛出异常 throw new JsonException($"无法将值转换为DateTime。格式应为'{_format}'或Unix时间戳。"); } public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) { // 序列化时也按指定格式写入 writer.WriteStringValue(value.ToString(_format)); } } // 使用方式1:通过特性标注在属性上 public class LogEntry { [JsonConverter(typeof(CustomDateTimeConverter))] public DateTime Timestamp { get; set; } } // 使用方式2:在JsonSerializerOptions中全局添加 var options = new JsonSerializerOptions(); options.Converters.Add(new CustomDateTimeConverter("dd/MM/yyyy"));

在 Newtonsoft.Json 中:同样需要JsonConverter,但API略有不同。

using Newtonsoft.Json; using Newtonsoft.Json.Converters; public class UnixTimestampConverter : JsonConverter<DateTime> { public override DateTime ReadJson(JsonReader reader, Type objectType, DateTime existingValue, bool hasExistingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Integer) { return DateTimeOffset.FromUnixTimeSeconds((long)reader.Value).UtcDateTime; } // 处理其他格式... throw new JsonSerializationException("Expected integer token for Unix timestamp."); } public override void WriteJson(JsonWriter writer, DateTime value, JsonSerializer serializer) { writer.WriteValue(new DateTimeOffset(value).ToUnixTimeSeconds()); } } // 使用:通过 [JsonConverter] 特性 public class Event { [JsonConverter(typeof(UnixTimestampConverter))] public DateTime EventTime { get; set; } }

4.2 处理多态类型(一个属性可能是多种类型)

例如,一个Data字段,可能是字符串,也可能是复杂对象。

定义模型:

public class Message { public string Type { get; set; } public IDataPayload Data { get; set; } // 接口类型 } public interface IDataPayload { } public class TextPayload : IDataPayload { public string Content { get; set; } } public class ImagePayload : IDataPayload { public string Url { get; set; } public int Width { get; set; } public int Height { get; set; } }

在 System.Text.Json 中实现转换器:System.Text.Json对多态的支持相对较弱,需要自定义转换器根据某个判别字段(如Type)来决定反序列化成哪种具体类型。

public class DataPayloadConverter : JsonConverter<IDataPayload> { public override IDataPayload Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { // 这里需要一个技巧:因为Data是Message的一部分,我们需要在Message的转换器中处理,或者使用JsonDocument先读取整个对象。 // 更常见的模式是在Message的转换器中,或者使用[JsonDerivedType]特性(.NET 7+)。 throw new NotImplementedException("需要在包含Type信息的上下文中处理。"); } public override void Write(Utf8JsonWriter writer, IDataPayload value, JsonSerializerOptions options) { JsonSerializer.Serialize(writer, value, value.GetType(), options); } } // .NET 7+ 提供了更优雅的原生支持:[JsonDerivedType] [JsonDerivedType(typeof(TextPayload), typeDiscriminator: "text")] [JsonDerivedType(typeof(ImagePayload), typeDiscriminator: "image")] public interface IDataPayload { } // 序列化/反序列化时会自动包含一个“$type”字段来区分。

在 Newtonsoft.Json 中:可以使用TypeNameHandling设置,但出于安全考虑,通常不建议对不可信的JSON源使用。更安全的做法是自定义转换器或使用JsonConverterCreate方法。

// 方法1:使用 TypeNameHandling (谨慎!) var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto // 自动在JSON中添加$type信息 }; // 反序列化时会根据$type创建正确类型。 // 方法2:自定义转换器(更安全) public class SafeDataPayloadConverter : JsonConverter { public override bool CanConvert(Type objectType) => objectType == typeof(IDataPayload); public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { // 假设JSON中有一个“type”字段 var jObject = JObject.Load(reader); var type = jObject["type"]?.ToString(); IDataPayload payload = type switch { "text" => new TextPayload(), "image" => new ImagePayload(), _ => throw new JsonSerializationException($"Unknown payload type: {type}") }; serializer.Populate(jObject.CreateReader(), payload); return payload; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { // 写入时也加上type字段 var type = value.GetType().Name; var jObject = JObject.FromObject(value); jObject.AddFirst(new JProperty("type", type.ToLowerInvariant())); jObject.WriteTo(writer); } }

4.3 处理JSON结构扁平化或嵌套

有时JSON的层级结构并不符合你的领域模型。例如,JSON是扁平的,但你想映射到一个嵌套的对象里。

原始JSON:

{ "sensorId": "s1", "locationBuilding": "A", "locationFloor": "3" }

目标C#类:

public class Device { public string SensorId { get; set; } public Location DeviceLocation { get; set; } } public class Location { public string Building { get; set; } public string Floor { get; set; } }

你可以通过自定义转换器,在反序列化时从扁平JSON中构造出嵌套的Location对象。但在很多情况下,更简单的做法是使用一个中间DTO(Data Transfer Object)类来接收扁平数据,然后再映射到你的领域模型。或者,使用像AutoMapper这样的对象映射库。对于简单的场景,System.Text.JsonNewtonsoft.Json都支持在属性上使用[JsonPropertyName][JsonProperty]来重命名,但无法处理这种结构转换。

一个折中的方案是使用JsonElement进行手动提取:

public class Device { public string SensorId { get; set; } [JsonIgnore] // 不直接反序列化这个属性 public Location DeviceLocation { get; set; } // 用于接收扁平字段的属性 [JsonPropertyName("locationBuilding")] public string LocationBuilding { get; set; } [JsonPropertyName("locationFloor")] public string LocationFloor { get; set; } // 在反序列化后,手动或通过方法构建Location [OnDeserialized] // System.Text.Json 使用 [JsonConstructor] 或自定义转换器逻辑 internal void OnDeserializedMethod() { DeviceLocation = new Location { Building = LocationBuilding, Floor = LocationFloor }; } }

处理不标准的JSON是对开发者耐心的考验,也是区分普通使用者和高级使用者的关键。核心思路是:优先尝试通过配置(命名策略、忽略规则)解决;复杂结构转换使用自定义转换器;极度复杂的映射考虑引入中间DTO层。

5. 实战集成:在真实C#项目场景中的应用与避坑

掌握了核心解析技术,最终要落地到项目里。无论是你提到的上位机软件、SCPI设备控制,还是Web API调用,JSON解析都是不可或缺的一环。这里分享几个典型场景下的实战经验和容易踩的坑。

5.1 场景一:上位机软件与设备通信(TCP/串口)

在上位机开发中,经常需要通过TCP/IP或串口与PLC、传感器、仪器(比如是德科技的设备,常使用SCPI或自定义协议)通信。现代设备越来越多地使用JSON作为数据交换格式。

典型流程:

  1. 设备端按一定频率或触发条件,将数据封装成JSON字符串发送。
  2. 上位机端接收字节流,需要先根据协议(如以换行符\n结尾、固定长度、或包含长度头)切分出完整的JSON报文。
  3. 对完整的JSON字符串进行解析。

关键坑点与解决方案:

  • 坑点1:粘包与拆包。TCP是流式协议,一次Receive可能收到多条消息的一部分,也可能收到多条完整消息。解决方案:定义明确的报文边界。最常用的方法是使用“长度头”或“分隔符”。

    • 长度头法:在JSON数据前加一个固定字节的长度字段。接收时先读长度,再读取指定长度的数据。
    • 分隔符法:约定以换行符(\n)或特定字符作为结束标志。使用ReadLineAsync或手动在缓冲区中查找分隔符。
  • 坑点2:编码问题。确保设备发送和上位机接收使用相同的字符编码,通常是UTF-8。解决方案:在接收和解析时明确指定编码。

    // 假设从NetworkStream中读取 using var streamReader = new StreamReader(networkStream, Encoding.UTF8); string jsonLine = await streamReader.ReadLineAsync(); // 如果使用换行符分隔 if (!string.IsNullOrEmpty(jsonLine)) { var data = JsonSerializer.Deserialize<DeviceData>(jsonLine); }
  • 坑点3:解析性能。如果数据频率很高(如毫秒级),解析可能成为瓶颈。解决方案

    • 使用System.Text.Json以获得最佳性能。
    • 对于固定格式的简单JSON,可以考虑使用Utf8JsonReader进行手动、零分配的解析,避免创建中间对象。
    • 将解析操作放在独立的线程或Task中,避免阻塞UI线程(对于WinForms/WPF上位机)。
  • 坑点4:错误处理与重连。网络可能中断,数据可能损坏。解决方案:反序列化时一定要用try-catch包裹,捕获JsonException。对于损坏的数据,记录日志并尝试跳过或请求重发。实现心跳机制和自动重连逻辑。

5.2 场景二:调用RESTful API与处理响应

这是最常见的场景。使用HttpClient调用API,然后解析返回的JSON响应。

标准做法:

using System.Net.Http.Json; // 这个命名空间提供了扩展方法 public class ApiService { private readonly HttpClient _httpClient; public ApiService(HttpClient httpClient) { _httpClient = httpClient; } public async Task<WeatherForecast?> GetWeatherAsync(string city) { try { // 使用 GetFromJsonAsync 一步到位,内部使用 System.Text.Json var forecast = await _httpClient.GetFromJsonAsync<WeatherForecast>($"/api/weather/{city}"); return forecast; } catch (HttpRequestException e) { // 处理网络错误 Console.WriteLine($"网络请求失败: {e.Message}"); } catch (JsonException e) { // 处理JSON解析错误 Console.WriteLine($"响应JSON格式错误: {e.Message}"); } return null; } }

进阶技巧与坑点:

  • 坑点1:配置JsonSerializerOptionsGetFromJsonAsyncPostAsJsonAsync等方法允许传入自定义的JsonSerializerOptions。如果你需要修改命名策略、添加转换器,务必在这里传入。

    var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, Converters = { new CustomDateTimeConverter("yyyy-MM-dd HH:mm:ss") } }; var data = await _httpClient.GetFromJsonAsync<MyModel>(url, options);
  • 坑点2:处理动态或未知结构的JSON。有时API返回的字段不确定。可以使用JsonDocumentJsonElementNewtonsoft.JsonJObject/JToken来动态查询。

    using var response = await _httpClient.GetAsync(url); var jsonString = await response.Content.ReadAsStringAsync(); using JsonDocument doc = JsonDocument.Parse(jsonString); if (doc.RootElement.TryGetProperty("data", out var dataElement) && dataElement.ValueKind == JsonValueKind.Array) { foreach (var item in dataElement.EnumerateArray()) { // 动态访问属性 if (item.TryGetProperty("name", out var nameProp)) { Console.WriteLine(nameProp.GetString()); } } }
  • 坑点3:流式处理大型API响应。如果API返回一个巨大的JSON数组,可以使用前面提到的DeserializeAsyncEnumerable

    using var response = await _httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); await using var stream = await response.Content.ReadAsStreamAsync(); await foreach (var item in JsonSerializer.DeserializeAsyncEnumerable<MyItem>(stream)) { // 逐项处理 }

5.3 场景三:配置文件与本地数据存储

将配置(如AppSettings.json)或本地缓存数据存储为JSON文件非常普遍。

  • ASP.NET Core 配置绑定:这是最经典的使用场景。框架底层使用System.Text.JsonNewtonsoft.Json(取决于配置)将appsettings.json反序列化到强类型的IConfiguration对象或选项类(IOptions)。你通常不需要手动解析。
  • 手动读写文件
    // 读取 string jsonText = await File.ReadAllTextAsync("config.json"); var config = JsonSerializer.Deserialize<AppConfig>(jsonText); // 写入(并格式化以便阅读) var options = new JsonSerializerOptions { WriteIndented = true }; string jsonToWrite = JsonSerializer.Serialize(config, options); await File.WriteAllTextAsync("config.json", jsonToWrite);
  • 坑点:文件锁与并发访问。如果多个进程或线程同时读写同一个JSON文件,会导致IO异常。解决方案:对于配置文件,通常采用“读多写少”的模式,写入时使用互斥锁或考虑使用专门的文件数据库(如SQLite)。对于缓存数据,可以考虑使用内存缓存+定期持久化的策略。

5.4 通用避坑指南总结

  1. 始终验证输入:不要信任任何外部JSON数据。反序列化前,如果可能,先验证其结构或使用JsonDocument进行初步检查。
  2. 处理异常:务必用try-catch包裹反序列化代码,捕获JsonException(System.Text.Json)或JsonSerializationException(Newtonsoft.Json),并给出有意义的错误日志。
  3. 注意循环引用:如果你的对象模型存在循环引用(如Parent.Child->Child.Parent),System.Text.Json默认会抛出异常。你需要配置JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve(.NET 6+)或使用Newtonsoft.Json并设置PreserveReferencesHandling
  4. 性能敏感处使用源生成器(Source Generators):.NET 6 为System.Text.Json引入了源生成器,它可以在编译时生成序列化/反序列化代码,完全避免运行时反射,能大幅提升性能。对于高频调用的类型,强烈建议使用。
    [JsonSerializable(typeof(MyPoco))] internal partial class MyJsonContext : JsonSerializerContext { } // 使用生成的上下文进行序列化,性能极佳 var json = JsonSerializer.Serialize(myPoco, MyJsonContext.Default.MyPoco);
  5. 版本兼容性:当你的C#模型类发生变化(增删属性)时,考虑使用[JsonExtensionData](System.Text.Json)或[JsonExtensionData](Newtonsoft.Json)来存储未知的JSON属性,避免反序列化失败,提高接口的向前/向后兼容性。

JSON解析在C#中是一个从入门到精深的技能。从简单的DeserializeObject到高性能流式处理,再到处理各种边界情况,每一步都对应着不同的应用场景和优化选择。希望这篇长文能帮你建立起完整的知识图谱,下次再遇到JSON时,能够游刃有余地选择最合适的工具和方法,写出既快又稳的代码。记住,没有最好的库,只有最适合当前场景的库。理解原理,灵活运用,才是王道。