关于 system-text-json-net11
面向编码 Agent 的技能,指导使用 .NET 11 的 System.Text.Json API 设置命名策略和处理类型元数据,并要求在 net11.0 上验证代码。
主要功能
- 01设置 JSON 属性的 PascalCase
- 02将字典键转换为 PascalCase
- 03获取强类型 JsonTypeInfo 元数据
- 04使用 TryGetTypeInfo 探测元数据
- 05运行并验证 net11.0 示例
适用场景
- 01用内置 API 替代自定义命名策略
- 02将字典键序列化为 PascalCase
- 03无需强制转换即可使用类型元数据
- 04无异常地检查元数据是否可用
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dotnet/ skills --skill system-text-json-net11安装后在 Claude Code 中输入/system-text-json-net11确认已加载。
详细介绍
System.Text.Json — .NET 11
.NET 11 为 System.Text.Json 新增了三个 API。本指南会准确说明何时使用每个 API、要编写什么、不要编写什么,以及如何验证结果确实可以运行。不要向用户介绍这些 API——直接应用它们,然后运行代码并展示输出。
| API | 替代 .NET 11 之前的变通方法…… |
|---|---|
JsonNamingPolicy.PascalCase(静态属性) | 编写自定义 JsonNamingPolicy 子类,或为每个成员手动添加 [JsonPropertyName] 注解 |
JsonSerializerOptions.GetTypeInfo<T>() | 调用非泛型的 GetTypeInfo(typeof(T)) 并强制转换为 JsonTypeInfo<T> |
JsonSerializerOptions.TryGetTypeInfo<T>(out JsonTypeInfo<T>? info) | 用 try/catch 包装 GetTypeInfo 以探测是否可用 |
步骤 0 — 确保可以执行所需的 net11.0 验证
这些 API 仅存在于 .NET 11 基类库中。编写代码之前:
- 运行
dotnet --list-sdks,确认已安装可面向net11.0的 SDK——11.xSDK,或带有net11.0目标包的更高版本 SDK。 - 如果用户明确要求运行示例,但没有安装合适的 SDK,请使用官方
dotnet-install脚本,将当前 .NET 11 SDK 安装到临时目录或项目本地目录。优先使用 GA 通道版本。仅当 GA 尚不可用或用户明确要求预览版时,才使用预览版。无需管理员权限,不要更改整台机器的PATH,也不要替换已安装的 SDK。 - 使用本地的
dotnet可执行文件运行示例。如果下载或执行受到阻止,仍然要提供完整的net11.0程序,并说明该程序未运行。绝不要用net10.0、自定义命名策略或其他 API 替代,并将其作为 .NET 11 功能的验证结果。
使用通道,不要猜测版本。先尝试 GA 通道:
$installScript = Join-Path $env:TEMP "dotnet-install-$([guid]::NewGuid()).ps1"
try {
Invoke-WebRequest -Uri 'https://dot.net/v1/dotnet-install.ps1' -OutFile $installScript
& $installScript -Channel 11.0 -InstallDir .\.dotnet
& .\.dotnet\dotnet.exe run --project <PATH_TO_NET11_PROJECT>
}
finally {
Remove-Item -LiteralPath $installScript -Force -ErrorAction SilentlyContinue
}
install_script="$(mktemp "${TMPDIR:-/tmp}/dotnet-install.XXXXXX")"
trap 'rm -f "$install_script"' EXIT
curl -fsSL https://dot.net/v1/dotnet-install.sh -o "$install_script"
bash "$install_script" --channel 11.0 --install-dir ./.dotnet
./.dotnet/dotnet run --project <PATH_TO_NET11_PROJECT>
在 .NET 11 GA 发布之前,请使用 -Quality preview(PowerShell)或
--quality preview(shell)重新尝试安装。
决策表 — 症状 → 执行此操作 → 绝不要这样做
根据用户的请求匹配一行,逐字应用“执行此操作”列,并在完成前确认“验证”列中的要求。
| 用户请求…… | 执行此操作(在 net11.0 上) | 绝不要这样做 | 验证 |
|---|---|---|---|
| PascalCase JSON 属性名 | options.PropertyNamingPolicy = JsonNamingPolicy.PascalCase; | 定义 class …: JsonNamingPolicy;为每个成员添加 [JsonPropertyName];自行转换名称的大小写 | 输出 JSON 的键为 PascalCase,例如 "Name"、"Age" |
| PascalCase 字典键 | options.DictionaryKeyPolicy = JsonNamingPolicy.PascalCase; | 只设置 PropertyNamingPolicy;预先转换字典;定义自定义策略 | pendingOrders 等字典键会变为 "PendingOrders" |
强类型元数据 JsonTypeInfo<T> | 设置 TypeInfoResolver = new DefaultJsonTypeInfoResolver(),然后执行 JsonTypeInfo<T> ti = options.GetTypeInfo<T>(); | (JsonTypeInfo<T>)options.GetTypeInfo(typeof(T)) | 变量的类型为 JsonTypeInfo<T>,无需强制转换 |
| 探测元数据是否已解析 | if (options.TryGetTypeInfo<T>(out var ti)) { … } else { … } | try { options.GetTypeInfo<T>(); } catch (…) { … } | 不使用 try/catch;处理两个分支 |
规则 1 — PascalCase 属性名
当用户希望输出的 JSON 属性名为 PascalCase(Name、Age),并询问内置或框架提供的方法时:
- 创建或复用一个
JsonSerializerOptions,并设置PropertyNamingPolicy = JsonNamingPolicy.PascalCase。 - 使用这些选项进行序列化。
不要编写 JsonNamingPolicy 子类,不要添加 [JsonPropertyName("…")]
特性来强制指定大小写,也不要手动将每个名称的首字母大写。在 .NET 11 中,JsonNamingPolicy.PascalCase 是唯一正确的做法。
// Console project (reflection enabled by default). To run this as a file-based app
// (dotnet run app.cs), also set TypeInfoResolver = new DefaultJsonTypeInfoResolver()
// — see "Producing runnable output" below.
using System.Text.Json;
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.PascalCase
};
string json = JsonSerializer.Serialize(new { name = "Jane", age = 30 }, options);
Console.WriteLine(json);
// {"Name":"Jane","Age":30}
字典键是单独的设置
PropertyNamingPolicy 不会转换 Dictionary<string, TValue> 的键。对于这种情况,请设置 DictionaryKeyPolicy:
var options = new JsonSerializerOptions
{
DictionaryKeyPolicy = JsonNamingPolicy.PascalCase
};
var values = new Dictionary<string, int>
{
["pendingOrders"] = 2,
["activeUsers"] = 5
};
Console.WriteLine(JsonSerializer.Serialize(values, options));
// {"PendingOrders":2,"ActiveUsers":5}
规则 2 — 强类型 JsonTypeInfo<T>
当用户希望获取 JsonTypeInfo<T> 类型的类型元数据(而不是需要强制转换的非泛型 JsonTypeInfo)时:
- 调用
options.GetTypeInfo<T>()— 它会直接返回JsonTypeInfo<T>。 - 将其赋值给
JsonTypeInfo<T>变量并使用它(例如,将其传递给JsonSerializer.Serialize/Deserialize)。
不要调用非泛型的 GetTypeInfo(Type) 重载并强制转换结果。
需要解析器。 除非选项中设置了
TypeInfoResolver,否则GetTypeInfo<T>()会抛出NotSupportedException(NoMetadataForType)——对于基于反射的应用,请设置TypeInfoResolver = new DefaultJsonTypeInfoResolver();对于经过裁剪/AOT 的应用,请使用源生成的JsonSerializerContext。
// File-based app (run: dotnet run app.cs). In a .csproj project, remove this line and
// set <TargetFramework>net11.0</TargetFramework> in the project file instead.
#:property TargetFramework=net11.0
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
var options = new JsonSerializerOptions
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver()
};
JsonTypeInfo<Person> typeInfo = options.GetTypeInfo<Person>();
Console.WriteLine(typeInfo.Type.Name); // Person
record Person(string Name, int Age);
规则 3 — 探测元数据且不抛出异常
当用户希望检查是否存在 T 的元数据并据此分支处理,且在元数据不存在时不抛出异常:
- 调用
options.TryGetTypeInfo<T>(out var info)。 - 显式处理
true分支(元数据已解析,使用info)和false分支 (未解析)。
不要用 try/catch 包装 GetTypeInfo<T>() 来检测缺失情况——这正是此 API 要消除的反模式。当没有解析器能为 T 生成元数据时,TryGetTypeInfo<T> 会返回 false(而不是抛出异常);这正是你希望据此分支处理的情况。
// File-based app (run: dotnet run app.cs). In a .csproj project, remove this line and
// set <TargetFramework>net11.0</TargetFramework> in the project file instead.
#:property TargetFramework=net11.0
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
// Configured with a resolver → metadata is available.
var configured = new JsonSerializerOptions
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver()
};
if (configured.TryGetTypeInfo<Person>(out JsonTypeInfo<Person>? info) && info is not null)
{
Console.WriteLine($"Resolved: {info.Type.Name}"); // Resolved: Person
}
else
{
Console.WriteLine("Type info not available");
}
// No resolver → TryGetTypeInfo returns false instead of throwing.
var empty = new JsonSerializerOptions();
Console.WriteLine(empty.TryGetTypeInfo<Person>(out _)); // False
record Person(string Name, int Age);
在 net11.0 上生成可运行的输出
只有程序能在 net11.0 上运行并打印 JSON,任务才算完成。优先使用控制台项目——基于反射的序列化在其中开箱即用。基于文件的应用也可行,但有一个重要注意事项(见下文)。
当已安装的 SDK 无法以 net11.0 为目标,且用户要求执行时,请使用官方脚本在本地安装 SDK,并通过完整路径调用它。安装脚本无需管理员权限,也不会持久修改 PATH。
选项 A — 控制台项目(推荐)
创建一个项目,其 .csproj 包含 <TargetFramework>net11.0</TargetFramework>,将代码放入 Program.cs,然后运行 dotnet run。确认进程以代码 0 退出并打印预期的 JSON。
using System.Text.Json;
var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.PascalCase };
Console.WriteLine(JsonSerializer.Serialize(new { name = "Jane", age = 30 }, options));
// {"Name":"Jane","Age":30}
选项 B — 基于文件的应用(最快,但有一个注意事项)
保存为 app.cs,然后运行 dotnet run app.cs;第一条指令会固定目标框架。
注意事项 — 基于文件的应用会禁用 System.Text.Json 反射。 在
dotnet run app.cs基于文件的应用中,JsonSerializer.IsReflectionEnabledByDefault为false,因此普通的 反射序列化会抛出NotSupportedException(NoMetadataForType)。请在选项中设置显式的TypeInfoResolver = new DefaultJsonTypeInfoResolver()(如下所示),或使用源生成的JsonSerializerContext。常规项目不需要这样做。
// File-based app (run: dotnet run app.cs). In a .csproj project, remove this line and
// set <TargetFramework>net11.0</TargetFramework> in the project file instead.
#:property TargetFramework=net11.0
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.PascalCase,
TypeInfoResolver = new DefaultJsonTypeInfoResolver()
};
Console.WriteLine(JsonSerializer.Serialize(new { name = "Jane", age = 30 }, options));
// {"Name":"Jane","Age":30}
完整示例 — 使用强类型元数据 + PascalCase 进行序列化
下面的记录特意使用小写成员名称,这样 PascalCase 策略在输出中重写名称时会清晰可见:
// File-based app (run: dotnet run app.cs). In a .csproj project, remove this line and
// set <TargetFramework>net11.0</TargetFramework> in the project file instead.
#:property TargetFramework=net11.0
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.PascalCase,
TypeInfoResolver = new DefaultJsonTypeInfoResolver()
};
JsonTypeInfo<Person> typeInfo = options.GetTypeInfo<Person>();
string json = JsonSerializer.Serialize(new Person("Jane", 30), typeInfo);
Console.WriteLine(json);
// {"Name":"Jane","Age":30}
record Person(string name, int age);
验证清单
报告成功之前,请确认所有适用项:
- 项目或基于文件的应用以
net11.0为目标(可在.csproj或#:property TargetFramework=net11.0指令中查看)。 - PascalCase 请求使用
JsonNamingPolicy.PascalCase——没有自定义的JsonNamingPolicy子类,也没有仅为更改大小写而添加的逐成员[JsonPropertyName]属性。 - 字典键请求设置
DictionaryKeyPolicy,而不只是PropertyNamingPolicy。 - 类型化元数据请求使用泛型
GetTypeInfo<T>()——不对非泛型JsonTypeInfo进行强制转换——且选项设置了TypeInfoResolver(例如DefaultJsonTypeInfoResolver),以免调用抛出NoMetadataForType。 - 探测请求使用
TryGetTypeInfo<T>(out …)——不在GetTypeInfo外层使用try/catch。 - 程序已实际运行(
dotnet run …),退出代码为 0,且打印的 JSON 显示了预期的属性名称(例如"Name"、"Age")。 - 如果使用基于文件的应用(
dotnet run app.cs),每个JsonSerializerOptions都设置了TypeInfoResolver——基于文件的应用会禁用反射,因此没有该设置的普通序列化会抛出NoMetadataForType。
常见问题
| 问题 | 修复方法 |
|---|---|
手动编写用于 PascalCase 的 class … : JsonNamingPolicy | 删除它;设置 PropertyNamingPolicy = JsonNamingPolicy.PascalCase。 |
为每个成员添加 [JsonPropertyName("Name")] 以强制更改大小写 | 删除这些属性;命名策略会一次性处理所有成员。 |
强制转换 (JsonTypeInfo<T>)options.GetTypeInfo(typeof(T)) | 调用泛型 options.GetTypeInfo<T>();无需强制转换。 |
使用 try { options.GetTypeInfo<T>(); } catch (…) { … } 测试是否可用 | 替换为 if (options.TryGetTypeInfo<T>(out var info)) { … }。 |
GetTypeInfo<T>() 引发 NotSupportedException / NoMetadataForType | 选项没有解析器。设置 TypeInfoResolver = new DefaultJsonTypeInfoResolver()(反射)或源生成的 JsonSerializerContext(修剪/AOT)。 |
在 dotnet run app.cs 基于文件的应用中,即使普通的 Serialize 也出现 NoMetadataForType | 基于文件的应用会禁用 STJ 反射。添加 TypeInfoResolver = new DefaultJsonTypeInfoResolver(),或将其作为普通项目运行。 |
| 让应用使用 SDK 的默认 TFM | 显式指定 net11.0,以解析 .NET 11 API,并在输出中显示目标框架。 |
| 未运行程序就声称成功 | 运行 dotnet run 并粘贴实际的 JSON 输出;目标是一个可运行且已执行的程序。 |
更多信息
- JsonNamingPolicy 类 — 内置命名策略,包括
PascalCase - JsonSerializerOptions.GetTypeInfo — 类型化和非类型化的元数据访问
- JsonTypeInfo<T> — 强类型序列化元数据
- DefaultJsonTypeInfoResolver —
GetTypeInfo/TryGetTypeInfo所需的基于反射的解析器 - 基于文件的应用 —
dotnet run app.cs和#:property指令