C# 试题 41:System.Text.Json
0801 以下代码输出什么?
难度: 实战
Console.WriteLine(JsonSerializer.Serialize(new { FirstName = "Ada", Age = 36 }));
- A.
{"FirstName":"Ada","Age":36},默认保留属性原名 - B.
{"FIRSTNAME":"ADA","AGE":36} - C.
{"firstName":"Ada","age":36},.NET 5+ 默认命名策略为 camelCase - D. 抛
JsonException,匿名类型不可序列化
查看答案与解析
正确答案C
解析: 自 .NET 5 起,JsonSerializerOptions 的默认命名策略是 camelCase,序列化时属性名 FirstName/Age 会写成 firstName/age。
关键点: 默认命名策略是 camelCase,不要以为默认输出 PascalCase。
0802 以下代码输出什么?
难度: 进阶
var p = JsonSerializer.Deserialize<Person>("""{"firstname":"Ada"}""");
Console.WriteLine(p?.FirstName ?? "null");
class Person { public string? FirstName { get; set; } }
- A. 输出
null,默认反序列化大小写敏感,firstname匹配不到FirstName - B. 输出
Ada - C. 抛
JsonException - D. 编译失败
查看答案与解析
正确答案A
解析: PropertyNameCaseInsensitive 默认为 false,反序列化时 JSON 属性名与成员名做大小写敏感匹配;firstname 匹配不到 FirstName,属性保持默认值 null。camelCase 命名策略只作用于序列化写出,不改变读取匹配。
关键点: 默认读取是大小写敏感的;需要宽松匹配要显式开启选项或使用 Web 默认。
0803 以下代码输出什么?
难度: 实战
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web);
var p = JsonSerializer.Deserialize<Person>("""{"firstName":"Ada"}""", options);
Console.WriteLine(p?.FirstName ?? "null");
class Person { public string? FirstName { get; set; } }
- A. 输出
null - B. 输出
Ada,Web 默认开启大小写不敏感匹配并采用 camelCase - C. 抛
JsonException - D. 编译失败
查看答案与解析
正确答案B
解析: JsonSerializerDefaults.Web 组合了 camelCase 命名策略、PropertyNameCaseInsensitive = true 与 NumberHandling.AllowReadingFromString 等 Web 场景默认值,firstName 能匹配到 FirstName。
关键点: Web 默认与 General 默认在大小写与数字处理上不同。
0804 以下代码运行结果是什么?
难度: 实战
var h = JsonSerializer.Deserialize<Holder>("""{"Value":"42"}""");
Console.WriteLine(h?.Value);
class Holder { public int Value { get; set; } }
- A. 输出
42 - B. 输出
0 - C. 输出
null - D. 抛
JsonException,默认不允许从字符串读取数字
查看答案与解析
正确答案D
解析: 默认情况下 JSON 字符串 "42" 不能赋给 int 属性,需要 JsonNumberHandling.AllowReadingFromString 才会把数字字符串读入数值成员,否则抛 JsonException。
关键点: 数字字符串默认读不进来,这是前后端联调常见报错源。
0805 以下代码输出什么?
难度: 实战
var p = JsonSerializer.Deserialize<Person>("""{"Name":"Ada","Extra":1}""");
Console.WriteLine(p?.Name ?? "null");
class Person { public string? Name { get; set; } }
- A. 抛
JsonException,未知成员必须处理 - B. 输出
Ada,默认未知成员被忽略 - C. 输出
null - D. 编译失败
查看答案与解析
正确答案B
解析: 默认策略对 JSON 中多余、未映射的成员直接忽略,不报错;只有开启 UnmappedMemberHandling.Disallow 等严格选项才会抛异常。
关键点: “多出来的字段”默认静默丢弃。
0806 开启严格选项后,以下代码运行结果是什么?
难度: 进阶
var options = new JsonSerializerOptions
{
UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow
};
var p = JsonSerializer.Deserialize<Person>("""{"Name":"Ada","Extra":1}""", options);
class Person { public string? Name { get; set; } }
- A. 抛
JsonException,严格模式下未知成员不被允许 - B. 正常反序列化,
Extra被忽略 - C. 输出
null - D. 编译失败,该枚举不存在
查看答案与解析
正确答案A
解析: UnmappedMemberHandling.Disallow 要求 JSON 的每个属性都能映射到目标成员,出现 Extra 这样的未知成员时抛 JsonException。它常用于服务端严格校验请求体。
关键点: 严格选项把“静默忽略”变成“显式报错”。
0807 以下代码运行结果是什么?
难度: 实战
var n = new Node { Name = "a" };
n.Next = n;
Console.WriteLine(JsonSerializer.Serialize(n));
class Node
{
public string? Name { get; set; }
public Node? Next { get; set; }
}
- A. 输出
{"Name":"a","Next":null} - B. 输出
{"Name":"a"} - C. 抛
JsonException,默认检测到对象循环引用 - D. 无限递归直到栈溢出
查看答案与解析
正确答案C
解析: 默认情况下 System.Text.Json 检测到循环引用会抛 JsonException(“A possible object cycle was detected”),不会无限递归,也不会静默写 null。
关键点: 循环引用默认是异常,不是跳过。
0808 使用 IgnoreCycles 后,以下代码输出什么?
难度: 进阶
var options = new JsonSerializerOptions
{
ReferenceHandler = ReferenceHandler.IgnoreCycles
};
var n = new Node { Name = "a" };
n.Next = n;
Console.WriteLine(JsonSerializer.Serialize(n, options));
class Node
{
public string? Name { get; set; }
public Node? Next { get; set; }
}
- A. 抛
JsonException - B. 输出
{"$id":"1","Name":"a","Next":{"$ref":"1"}} - C. 输出
{"Name":"a"} - D. 输出
{"Name":"a","Next":null},循环引用位置写 null,不抛异常
查看答案与解析
正确答案D
解析: ReferenceHandler.IgnoreCycles 在遇到循环引用时把该成员写成 null 并继续,不抛异常;注意它不产生 $id/$ref 元数据,反序列化时无法还原共享引用。
关键点: IgnoreCycles 适合“只求不炸”的场景,会丢失引用信息。
0809 使用 Preserve 后,以下代码输出什么?
难度: 实战
var options = new JsonSerializerOptions
{
ReferenceHandler = ReferenceHandler.Preserve
};
var n = new Node { Name = "a" };
n.Next = n;
Console.WriteLine(JsonSerializer.Serialize(n, options));
class Node
{
public string? Name { get; set; }
public Node? Next { get; set; }
}
- A. 抛
JsonException - B. 输出包含
$id与$ref元数据(如{"$id":"1","Name":"a","Next":{"$ref":"1"}}) - C. 输出与默认选项一致
- D. 输出
{"Name":"a","Next":null}
查看答案与解析
正确答案B
解析: ReferenceHandler.Preserve 为对象分配 $id,后续重复引用用 $ref 指向它,从而保留引用结构(包括循环与共享对象);代价是 JSON 里出现框架元数据字段。
关键点: Preserve 保留引用图,但会改变 JSON 形态。
0810 使用 ReferenceHandler.Preserve 序列化 Dictionary<int, string> 时,会发生什么?
难度: 进阶
- A. 由于 JSON 对象键必须是字符串,非字符串键字典会序列化为 KeyValuePair 对象数组(如
[{"Key":1,"Value":"one"}]) - B. 键自动转换为字符串,输出
{"1":"one"} - C. 抛
NotSupportedException - D. 忽略该字典
查看答案与解析
正确答案A
解析: 开启 Preserve 后,引用跟踪依赖 $id/$ref,无法与“键即字符串”的 JSON 对象形式共存,因此非字符串键字典退化为 KeyValuePair 对象数组;只有字符串键字典保持对象形态。
关键点: Preserve 会改变非字符串键字典的 JSON 结构。
0811 以下自定义转换器能否编译?
难度: 进阶
class IntToStringConverter : JsonConverter<int>
{
public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> reader.GetInt32();
}
- A. 编译通过,Write 有默认实现(抛
NotSupportedException) - B. 编译通过,序列化时自动回退默认转换器
- C. 编译通过,序列化时忽略该转换器
- D. 编译失败,
JsonConverter<T>的 Read 与 Write 都是抽象成员,必须全部实现
查看答案与解析
正确答案D
解析: JsonConverter<T> 的 Read 与 Write 都是抽象方法,只实现 Read 会报“未实现抽象成员”的编译错误。注意非泛型基类 JsonConverter 的 Read/Write 才是带默认实现的虚方法。
关键点: 自定义泛型转换器必须同时实现 Read 与 Write。
0812 属性上的 [JsonConverter(typeof(X))] 与 options.Converters 注册的转换器同时存在时,哪个生效?
难度: 实战
- A.
options.Converters总是优先 - B. 抛
InvalidOperationException(注册冲突) - C. 属性上的
JsonConverterAttribute优先于 options 注册的转换器 - D. 由转换器类型决定,规则不确定
查看答案与解析
正确答案C
解析: 转换器选择优先级是:属性上的 [JsonConverter] > options 的 Converters 集合 > 类型上的 [JsonConverter] > 内置转换器。因此属性级标注优先。
关键点: 属性级转换器优先级最高。
0813 使用源生成 context 序列化未注册的类型,结果是什么?
难度: 进阶
var order = new Order();
Console.WriteLine(JsonSerializer.Serialize(order, typeof(Order), AppJsonContext.Default));
[JsonSerializable(typeof(Person))]
internal partial class AppJsonContext : JsonSerializerContext { }
- A. 编译失败,Order 未注册
- B. 运行抛
InvalidOperationException,context 中没有 Order 的元数据 - C. 自动回退到反射序列化,正常输出
- D. 输出空对象
{}
查看答案与解析
正确答案B
解析: 传入 JsonSerializerContext 后,序列化只使用该 context 的元数据;Order 未加 [JsonSerializable],context 查不到元数据,抛 InvalidOperationException,不会回退反射。
关键点: 源生成不会“顺带”处理未声明类型。
0814 以下代码输出什么?
难度: 实战
var p = new Person { Name = "Ada" };
Console.WriteLine(JsonSerializer.Serialize(p));
[JsonSerializable(typeof(Person))]
internal partial class AppJsonContext : JsonSerializerContext { }
- A. 输出
{"name":"Ada"},未传 context 时走反射序列化,源生成不自动生效 - B. 抛
InvalidOperationException,必须使用源生成 - C. 输出
{"Name":"Ada"} - D. 编译失败
查看答案与解析
正确答案A
解析: 不传入 context 或 TypeInfo 时,JsonSerializer.Serialize 使用反射元数据,源生成代码不会自动接管;反射路径在普通运行时可用,但在 AOT/裁剪场景会受限。
关键点: 源生成必须显式传入 context 或 TypeInfo 才生效。
0815 开启 RespectNullableAnnotations 后,以下代码运行结果是什么?
难度: 进阶
var options = new JsonSerializerOptions { RespectNullableAnnotations = true };
var p = JsonSerializer.Deserialize<Person>("""{"Name":null}""", options);
class Person { public string Name { get; set; } = ""; }
- A. 正常反序列化,Name 为 null
- B. 输出空字符串
- C. 编译失败,该选项不存在
- D. 抛
JsonException,严格模式下 null 不能赋给非空引用类型
查看答案与解析
正确答案D
解析: RespectNullableAnnotations 开启后,反序列化器会尊重可空注解:JSON null 写入非空引用类型属性(或非空值类型)时抛 JsonException,而不是静默接受。
关键点: 严格选项把“隐式 null”变成异常,帮助提前暴露契约违例。
0816 以下代码输出什么?
难度: 实战
var p = JsonSerializer.Deserialize<Person>("""{"Name":"Ada"}""");
Console.WriteLine(p?.Name ?? "null");
class Person
{
public string? Name { get; private set; }
}
- A. 输出
Ada - B. 抛
JsonException,私有 setter 导致无法写入 - C. 输出
null,默认反序列化不使用私有 setter - D. 编译失败
查看答案与解析
正确答案C
解析: 默认情况下 System.Text.Json 只使用公共 setter/init,私有 setter 不会参与反序列化,Name 保持默认值 null;需要填充非公共成员必须加 [JsonInclude]。
关键点: 私有 setter 默认不写,别指望反序列化器自动绕过访问级别。
0817 以下代码输出什么?
难度: 进阶
var p = JsonSerializer.Deserialize<Point>("""{"x":1,"y":2}""");
Console.WriteLine(p?.X + "," + p?.Y);
class Point
{
public Point(int x, int y) { X = x; Y = y; }
public int X { get; }
public int Y { get; }
}
- A. 抛
JsonException,构造函数参数匹配大小写敏感 - B. 输出
1,2,构造函数参数与 JSON 属性名的匹配大小写不敏感 - C. 输出
0,0 - D. 编译失败,需要
[JsonConstructor]
查看答案与解析
正确答案B
解析: 反序列化使用带参构造函数时,JSON 属性名与构造函数参数名的匹配是大小写不敏感的(与 PropertyNameCaseInsensitive 无关),x/y 都能命中参数。
关键点: 构造参数匹配与普通属性匹配的大小写规则不同。
0818 以下代码输出什么?
难度: 实战
Console.WriteLine(JsonSerializer.Serialize(new Person { FullName = "Ada" }));
class Person
{
[JsonPropertyName("full_name")]
public string FullName { get; set; } = "";
}
- A. 输出
{"full_name":"Ada"},JsonPropertyName 优先于命名策略 - B. 输出
{"fullName":"Ada"} - C. 输出
{"FullName":"Ada"} - D. 编译失败
查看答案与解析
正确答案A
解析: [JsonPropertyName] 显式指定的名称优先级高于命名策略,camelCase 不会改写它,因此输出 full_name。
关键点: 显式标注压过策略;需要自定义名称时用它而不是改策略。
0819 以下代码输出什么?
难度: 进阶
Animal a = new Dog { Name = "Rex", Barks = true };
Console.WriteLine(JsonSerializer.Serialize(a));
class Animal { public string Name { get; set; } = ""; }
class Dog : Animal { public bool Barks { get; set; } }
- A. 输出
{"Name":"Rex","Barks":true} - B. 输出
{"Barks":true} - C. 输出
{"Name":"Rex"},默认按声明类型序列化,多态需要[JsonDerivedType] - D. 抛
JsonException
查看答案与解析
正确答案C
解析: 默认情况下 System.Text.Json 按声明类型(Animal)序列化,派生类属性 Barks 不会出现;要输出派生属性需要 [JsonDerivedType] 声明多态(会附带判别字段)。
关键点: 多态序列化是显式开启的,默认只输出声明类型的成员。
0820 关于 JsonSerializerOptions 的复用,哪项正确?
难度: 进阶
- A. 每次序列化都新建 options 更安全,成本可忽略
- B. options 在首次使用后仍可随意修改
- C. 同一个 options 不能同时用于序列化与反序列化
- D. 首次使用后 options 会被锁定,修改抛
InvalidOperationException;复用单例可避免反复构建内部转换器缓存
查看答案与解析
正确答案D
解析: options 首次参与序列化/反序列化后会构建内部转换器映射并进入只读状态,再修改任何属性都会抛 InvalidOperationException;每次新建也会重复构建缓存,因此应复用单例。
关键点: 把 options 当作“配置一次、全局复用”的不可变对象。