JSON 转 C# 类实战:属性命名、类型推断与省去手写模型
把一段 JSON 粘进来就拿到带 PascalCase 属性、JsonProperty 特性和嵌套类的 C# class,讲清类型推断规则、System.Text.Json 反序列化怎么绑、以及怎样不再手写一行模型。
JSON 转 C# 类:从一段响应体到可直接反序列化的模型
接第三方接口时最磨人的环节,往往不是写业务逻辑,而是照着一段 JSON 响应,一行一行把对应的 C# class 敲出来。GitHub 的 user 对象有三十多个字段,Stripe 的 webhook 嵌套好几层,你得逐个判断:这个键大写后叫什么、是 int 还是 long、要不要可空、嵌套对象拆不拆成独立类。敲完还得检查有没有写错大小写,否则反序列化时字段悄悄变成默认值都查不出来。
这件事完全可以交给工具。把 JSON 粘进 JSON 转 C# 类,立刻拿到一组干净的 class 声明,属性是 PascalCase,该带的特性都带上,嵌套对象自动抽成独立类型。下面把它背后的几条规则讲透,你用起来才知道每个输出为什么是那样。
属性首字母大写,与原始键名分离
C# 的约定是属性首字母大写,而 JSON 的键名常常是 snake_case 或 camelCase。工具会把每个键转成合法的 C# 标识符:site_admin 变 SiteAdmin,is-active 变 IsActive,userId 变 UserId。连字符、下划线这些在 C# 里不合法的字符会被吃掉,剩下的按词首大写拼接。
关键在于:C# 的属性名跟线上的键名一旦不一致,反序列化就需要一座桥。这座桥就是 [JsonPropertyName](System.Text.Json)或 [JsonProperty](Newtonsoft)特性。
类型推断:int、long 还是 double
JSON 只有一种数字类型,但 C# 分得很细。工具按样本值推断:能放进 Int32 区间(−2,147,483,648 到 2,147,483,647)的整数推断成 int;超出这个范围的大整数,比如毫秒级时间戳、雪花 ID、字节数,推断成 long;带小数的推断成 double。
还有一个常被忽略的细节:如果同一个字段在某个样本里是整数、在另一个样本里带小数,类型会放宽到 double,这是安全的超集。因为 C# 在反序列化时不会隐式收窄,选窄了反而会抛异常。
可空也是推断出来的。一个键在对象数组的部分元素里有、部分元素里没有,或者出现过 null,开启可空开关后就输出成 T?,例如 int?、DateTime?、string?。
一段真实输入到输出
输入这段 JSON:
{
"id": 1296269,
"html_url": "https://github.com/octocat/Hello-World",
"created_at": "2011-01-26T19:01:12Z",
"site_admin": false
}
根类名设为 Repo,选 System.Text.Json,开启识别日期,得到:
using System;
using System.Text.Json.Serialization;
public class Repo
{
public int Id { get; set; }
[JsonPropertyName("html_url")]
public string HtmlUrl { get; set; }
[JsonPropertyName("created_at")]
public DateTime CreatedAt { get; set; }
[JsonPropertyName("site_admin")]
public bool SiteAdmin { get; set; }
}
注意 Id 没有特性,因为 id 大写成 Id 后,System.Text.Json 默认就能匹配上;而 html_url、created_at、site_admin 改名后形状不一致,各自挂上特性才绑得回去。created_at 是 ISO-8601 字符串,识别成了 DateTime。只有输出真正用到的 using 才会被列出来。
为什么 System.Text.Json 离不开这个特性
System.Text.Json 默认大小写敏感,这跟 Newtonsoft(默认大小写不敏感)是两套脾气。也就是说,没有 [JsonPropertyName("site_admin")],序列化器根本不会把线上的 snake_case 字段绑到你的 SiteAdmin 属性上,反序列化后它会静静地停在默认值 false,你的代码逻辑全跑通,数据却是错的,这种 bug 最难排。
我自己第一次踩这个坑是在一个 ASP.NET Core 项目里,后端日志一切正常,前端就是收不到某个布尔位。查了大半天才反应过来:是属性名大小写对不上,System.Text.Json 默默吞了。从那以后我接任何接口都先用这个工具把模型生成出来,特性一个不漏,省下的不只是打字时间,更是这种隐性 bug 的排查成本。
嵌套与数组:折叠成一个共享类
嵌套对象会自动抽成独立的类,命名沿用父级,比如 Repo 里的 owner 字段会生成 RepoOwner。对象数组更聪明:[{"a":1},{"a":1,"b":2}] 不会生成两个几乎重复的类,而是折叠成一个同时带 a 和 b 的类型,缺失的 b 标成可空。这正是手写时最容易漏掉的归并工作。
想先把杂乱的响应体理顺再生成,可以先过一道 JSON 格式化工具 看清结构;对比两版接口字段差异时,JSON 差异对比 能直接标出新增和删除的键。
不只 C#,一套思路通吃多语言
类型推断、命名转换、嵌套折叠这套逻辑,换个目标语言同样成立。后端是 Go 就用 JSON 转 Go 结构体,前端要对齐类型定义就用 JSON 转 TypeScript 接口,Python 服务用 JSON 转 Python dataclass。同一份接口契约,在每一端都拿到强类型模型,字段名一改,编译器当场报错,而不是上线后才在运行时炸。
手写模型这件事,本质上是机械劳动,把它交出去,你才有精力盯真正要紧的业务边界。
Made by Toolora · Updated 2026-06-13