practical-aspnetcore:使用 System.Text.Json 自定义 SnakeCaseNamingPolicy 实现 snake_case JSON 命名(json-7 实战) 示例工程【免费下载链接】practical-aspnetcorePractical samples of ASP.NET Core 11, 10, 9, 8.0, 7.0, 6.0, 5.0, 3.1, 2.2, and 2.1,projects you can use. Readme contains explanations on all projects.项目地址https://gitcode.com/gh_mirrors/pr/practical-aspnetcore点击查看免费下载本篇基于 practical-aspnetcore 仓库中的 json-7 示例讲解如何为System.Text.Json实现一个自定义命名策略JsonNamingPolicy让序列化输出的 JSON 属性名与字典键全部转为 snake_case 形式。读完本篇你将掌握完整的SnakeCaseNamingPolicy实现代码、底层状态机的逐状态解析以及仓库中 json-8 对两种 snake_case 转换实现的 Benchmark 对比方式。示例目标与运行环境json-7 的原始说明只有一句话概括Create a custom naming policy that generate JSON property names in snake_case.Code for converting CamelCase property to snake_case is obtained from Newtonsoft.Json.即编写一个自定义命名策略类使System.Text.Json生成的 JSON 属性名遵循 snake_case 规则其中 CamelCase 转 snake_case 的核心算法取自 Newtonsoft.Json 的StringUtils.ToSnakeCase实现见 json-7 主程序 中的出处注释。运行环境方面从 json-7.csproj 看目标框架为net10.0LangVersion设为preview仓库根目录 global.json 锁定 SDK 版本10.0.0rollForward: major、allowPrerelease: true因此在 .NET 10 SDK或允许前向滚动的主版本 SDK环境下dotnet run即可运行。完整示例代码json-7 的 Program.cs 是一个标准的 Minimal API 单文件应用结构分为三部分一个 GET 端点演示序列化效果、一个StringUtils静态类提供 snake_case 转换算法、一个SnakeCaseNamingPolicy策略类。1. 用 JsonSerializerOptions 挂载命名策略端点部分使用匿名类型构造 payload并传入JsonSerializerOptionsProgram.cs#L6-L33var app WebApplication.Create(); app.MapGet(/, () { var payload new { Name Annie, Age 33, IsMarried false, CurrentTime DateTimeOffset.UtcNow, Characters new { Funny true, Feisty true, Brilliant true, FOMA false, HighEmpathy true } }; var options new JsonSerializerOptions { WriteIndented true, DefaultIgnoreCondition JsonIgnoreCondition.WhenWritingNull, PropertyNamingPolicy new SnakeCaseNamingPolicy(), DictionaryKeyPolicy new SnakeCaseNamingPolicy() }; return Results.Json(payload, options); }); app.Run();这里有两个策略挂载点与 json-2 示例的说明一致PropertyNamingPolicy作用于对象属性名。若不设置属性会按 C# 的 PascalCase 原样输出内置策略里最常用的替代是JsonNamingPolicy.CamelCase而 snake_case 没有内置策略所以需要自己实现DictionaryKeyPolicy作用于字典键。本示例中Characters虽是匿名类型但仓库的姊妹示例 json-2 的 Program.cs 里Characters是Dictionarystring, bool其键Funny、FOMA等同样需要策略转换因此两项都设置为new SnakeCaseNamingPolicy()。payload 特意选用了多种形态的属性名Name、IsMarried、连续大写的FOMA、复合词HighEmpathy恰好覆盖了 snake_case 转换的几类边界情况方便直接观察输出。2. 策略类一行委托给转换算法public class SnakeCaseNamingPolicy : JsonNamingPolicy { public override string ConvertName(string name) { return StringUtils.ToSnakeCase(name); } }JsonNamingPolicy是抽象基类只需重写ConvertName(string name)返回转换后的属性名。这里把真正的转换逻辑委托给下面的StringUtils。3. snake_case 转换算法来自 Newtonsoft.Json 的状态机转换算法直接移植自 Newtonsoft.Json 的StringUtils.ToSnakeCaseProgram.cs#L38-L119文件内注释注明了原始出处为 Newtonsoft.Json 仓库的StringUtils.cs并标注 Modified to use span。核心是一个四状态状态机internal static class StringUtils { internal enum SnakeCaseState { Start, Lower, Upper, NewWord } public static string ToSnakeCase(string s) { if (string.IsNullOrEmpty(s)) { return s; } StringBuilder sb new StringBuilder(); SnakeCaseState state SnakeCaseState.Start; for (int i 0; i s.Length; i) { if (s[i] ) { if (state ! SnakeCaseState.Start) { state SnakeCaseState.NewWord; } } else if (char.IsUpper(s[i])) { switch (state) { case SnakeCaseState.Upper: bool hasNext (i 1 s.Length); if (i 0 hasNext) { char nextChar s[i 1]; if (!char.IsUpper(nextChar) nextChar ! _) { sb.Append(_); } } break; case SnakeCaseState.Lower: case SnakeCaseState.NewWord: sb.Append(_); break; } char c; #if HAVE_CHAR_TO_LOWER_WITH_CULTURE c char.ToLower(s[i], CultureInfo.InvariantCulture); #else c char.ToLowerInvariant(s[i]); #endif sb.Append(c); state SnakeCaseState.Upper; } else if (s[i] _) { sb.Append(_); state SnakeCaseState.Start; } else { if (state SnakeCaseState.NewWord) { sb.Append(_); } sb.Append(s[i]); state SnakeCaseState.Lower; } } return sb.ToString(); } }四个状态的含义状态含义Start起始状态或者刚处理完一个下划线后的状态Lower刚输出过一个小写字符即处于普通单词内部Upper刚输出过一个刚被转成小写的字母即处于连续大写的尾部NewWord刚遇到一个空格下一个字符前需要补一个下划线逐分支解读遇到空格s[i] 若不在Start态则切换到NewWord等下一个字符落笔前补上_。这保证New Word这类带空格的名称能转换为new_word。遇到大写字母char.IsUpper(s[i])这是状态机的核心分两种情形当前处于Upper态前一个字符也是大写需要判断这是连续缩写还是缩写结束、新词开始。当i 0且还有下一个字符时若下一字符不是大写也不是下划线例如FOMA场景里的倒数第二个大写字母才在此处补_。这一条正是处理SocialSecurityNumber→social_security_number这类连续缩写词的关键当前处于Lower或NewWord态前一个字符是小写或刚结束一个词直接补_例如IsMarried中M之前的那个_。随后把该大写字母转小写代码用char.ToLowerInvariant并保留了一个HAVE_CHAR_TO_LOWER_WITH_CULTURE条件编译分支用于文化敏感场景后追加状态切到Upper。遇到下划线s[i] _原样追加并回到Start态即原有的下划线不会被破坏。其他字符小写字母、数字等若处于NewWord态先补_然后原样追加状态切到Lower。对照示例 payload 的转换结果用示例中的属性名走一遍状态机可得原始属性名转换结果关键路径Namename单首字母大写无分支插入IsMarriedis_marriedM前状态为Lower插入_FOMAfoma全程Upper态下一字符均为大写不插_HighEmpathyhigh_empathyE前状态为Lower插入_SocialSecurityNumbersocial_security_numberS/N前状态为Lower插入_ecurit与umber交界处靠Upper分支的下一字符非大写规则补上最后一个_json-8 的 Benchmark 正是用这个输入做测试用例这个连续大写 小写收尾的边界规则正是朴素正则式拆分难以正确处理的场景也是移植 Newtonsoft 成熟实现而非简单Regex.Replace的原因。姊妹示例 json-8两种转换实现的 Benchmark 对比仓库中 json-8 专门对CamelCase 转 snake_case 的两种实现做了 Benchmark 对比与本示例算法直接相关第一种即上文这套来自 Newtonsoft.Json 的状态机实现在 json-8 的 Program.cs 中命名为ToSnakeCaseNewtonsoft第二种是一行 LINQ 实现public static string ToSnakeCaseLinq(string s) { return string.Concat(s.Select((x, i) i 0 char.IsUpper(x) ? _ x.ToString() : x.ToString())).ToLower(); }两种实现均用固定输入SocialSecurityNumber作为基准用例Program.cs#L108-L118。json-8 是基于 BenchmarkDotNetjson-8.csproj 中引用BenchmarkDotNet0.15.8的控制台应用运行方式为其 README 给出的两条命令dotnet build -configuration Release dotnet run从 json-8 的代码结构看这个对比的意义在于当命名策略被挂到JsonSerializerOptions上后ConvertName会在每次序列化时按属性调用算法本身的效率会体现在高吞吐 API 场景里。需要说明的是仓库中并未附带 Benchmark 的结果数据上述两种实现的取舍需读者自行按 json-8 的方法在本地运行验证。如何运行 json-7 并验证输出进入 projects/json/json-7 目录后dotnet run然后访问根路径/即可看到带缩进WriteIndented true的 JSON属性名和嵌套属性名均已是 snake_case 形态例如is_married、current_time、high_empathy。注意DefaultIgnoreCondition JsonIgnoreCondition.WhenWritingNull的作用值为 null 的属性不会写入输出这与 json-2 的选项说明中IgnoreNullValues一项描述的行为对应。适用前提与注意事项该策略是全局生效于传入的JsonSerializerOptions的只要PropertyNamingPolicy挂上了SnakeCaseNamingPolicy同一 options 下所有对象属性包括嵌套对象都会按 snake_case 输出如需按接口或按端点差异化可在不同端点构造不同的JsonSerializerOptions如本示例所示在 handler 内构造。算法移植自 Newtonsoft.JsonToSnakeCase对空字符串直接原样返回且保留原有下划线因此对已是 snake_case 的输入基本是幂等的。适用框架前提示例目标为net10.0见 json-7.csproj由于JsonNamingPolicy与Results.Json均为较早期版本已具备的 API该思路在更早的 .NET 版本上同样适用仅 SDK 版本需与global.json的约束兼容。在 practical-aspnetcore 的 System.Text.Json 示例系列 中json-7 处于自定义命名策略这一环可与前序的 json-2JsonSerializerOptions 选项 和后序的 json-8snake_case 转换 Benchmark 对照阅读形成选项配置 → 自定义策略 → 性能对比的完整链路。赞分享示例工程【免费下载链接】practical-aspnetcorePractical samples of ASP.NET Core 11, 10, 9, 8.0, 7.0, 6.0, 5.0, 3.1, 2.2, and 2.1,projects you can use. Readme contains explanations on all projects.项目地址https://gitcode.com/gh_mirrors/pr/practical-aspnetcore点击查看免费下载相关推荐使用 System.Text.Json 的 JsonObject 构建 JSON 文档practical-aspnetcore 可写 JSON DOM 实战使用 System.Text.Json 的 JsonObject 构建 JSON 文档practical aspnetcore 可写 JSON DOM 实战示例工程practical-aspnetcore 实战用 JsonSerializerOptions 精细控制 System.Text.Json 的 JSON 输出practical aspnetcore 实战用 JsonSerializerOptions 精细控制 System.Text.Json 的 JSON 输出示例工程practical-aspnetcore 实战用 System.Text.Json 的 JsonArray 构建可写 JSON 文档Writable DOMpractical aspnetcore 实战用 System.Text.Json 的 JsonArray 构建可写 JSON 文档Writable DOM示例工程上一篇modetest 命令详解用 libdrm 模式测试工具验证 DRM/KMS 显示驱动linux-command 速查手册下一篇Apache Airflow 任务组拓扑排序优化pass-number 遍历如何消除反向声明 DAG 的 O(N²) 最坏情况创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考