打开网易新闻 查看精彩图片

99%的C#项目都在用同一行代码埋雷:JsonSerializer.Deserialize(json)。这行代码背后藏着.NET生态最大的技术债务——反射(reflection,运行时类型检查机制)驱动的隐式反序列化。作者Louis Pilfold在文中算了一笔账:一个中等规模的微服务,启动时要扫描上千个POCO属性,内存里堆满临时字符串,CPU在反射调用中反复横跳。

更隐蔽的是类型安全幻觉。你以为public int Id { get; set; }保证了Id一定是整数?JSON里塞个字符串"10",System.Text.Json默默帮你转换。塞个null?属性原样保留默认值0。这种"贴心"让无数生产事故在代码审查阶段完全隐身。

POCO不是解药,是麻醉剂

POCO不是解药,是麻醉剂

行业惯例要求用POCO(plain old C# object,纯C#对象)隔离序列化层与领域层。作者干了十年C#,现在回头看:这套做法像在漏水的船底铺地毯。POCO确实解耦了,但把验证责任推给了运行时——某个字段改名、某个嵌套结构扁平化,编译器零提示,线上直接炸。

Newtonsoft.Json和System.Text.Json的对比更有意思。前者是老江湖,配置项多到能写本书;后者是微软亲儿子,性能快了三倍,但"默认宽容"的脾气一点没改。两者共享同一个底层假设:开发者希望反序列化"Just Works"。

作者举了个典型场景。假设API返回的address.number从字符串变成整数,POCO里的string Number会当场抛异常——如果开了严格模式的话。没开?那你会得到一个空字符串,下游逻辑继续跑,数据污染像病毒一样扩散。

函数式语言的"显式解码"是什么体验

函数式语言的"显式解码"是什么体验

作者现在主力用Gleam,这门语言的JSON处理彻底抛弃了反射。没有POCO,没有自动映射,每个字段必须手写解码器(decoder)。代码量确实多了,但错误处理从运行时前移到编译期——JSON结构对不上?类型检查直接拦下。

这种范式转换的核心差异:C#把反序列化当成"数据搬家",Gleam把它当成"数据谈判"。谈判就得逐条确认:这个字段存在吗?类型匹配吗?转换失败怎么办?作者形容这是"从自动驾驶切到手动档",但"至少你知道轮子在哪"。

显式解码的另一个副作用是性能。Gleam编译器能内联解码器,消除反射开销。作者没给具体数字,但提到一个观察:同样解析10MB的JSON日志,Gleam服务的内存曲线比C#版本平缓得多,GC(垃圾回收)压力不在一个量级。

折中方案:C#也能部分显式

折中方案:C#也能部分显式

不想换语言?作者给了两条退路。System.Text.Json的JsonConverter可以接管特定类型的反序列化逻辑,手写验证代码。或者更激进一点——放弃自动反序列化,先用JsonDocument解析成DOM(文档对象模型),再逐字段提取并转换。

两条路都牺牲便利性换可靠性。作者自己的经验是:核心领域模型必须走显式路线,边缘DTO可以保留自动映射。"不是非黑即白,但得知道自己在妥协什么"。

文末作者留了句话:「这些想法是我职业生涯中慢慢形成的,对你未必适用,但值得实验。」没有结论,没有号召,只有一个老程序员对技术选型的诚实复盘。你的项目里,最近一次JSON解析异常是在测试阶段还是生产环境发现的?