C#使用System.Text.Json反序列化PagedResult返回默认值问题
问题描述
测试System.Text.Json的序列化与反序列化功能时,反序列化得到的PagedResult对象仅包含默认值,导致测试失败。已实现PagedResultBase抽象类、PagedResult<T>泛型类、UserDtoTest类及测试代码,且必须使用System.Text.Json.Serialization。目前发现两种可能的解决方案:一是为每个属性添加[JsonPropertyName("currentPage")]这类特性;二是添加驼峰命名选项。
问题原因
- 命名规则不匹配:类中属性采用PascalCase命名(如
CurrentPage),而JSON字符串中使用camelCase命名(如currentPage)。默认情况下,System.Text.Json不会自动映射两种命名规则的属性。 - 只读属性无法反序列化:
PagedResultBase中的属性只有get访问器,没有set访问器。System.Text.Json默认不会对只读属性进行反序列化赋值。 - 计算属性冗余:
IsEmpty和IsNotEmpty是依赖Items推导的计算属性,但JSON中包含这两个字段。反序列化时无法为计算属性赋值,且序列化时默认不会输出这类无set访问器的计算属性,导致序列化后的JSON与原始JSON不一致。
解决方案
方案一:显式指定属性映射特性
为每个属性添加[JsonPropertyName]特性匹配JSON字段名,同时通过构造函数让System.Text.Json能反序列化只读属性:
- 修改
PagedResultBase类:
public abstract class PagedResultBase { [JsonPropertyName("currentPage")] public int CurrentPage { get; } [JsonPropertyName("resultsPerPage")] public int ResultsPerPage { get; } [JsonPropertyName("totalPages")] public int TotalPages { get; } [JsonPropertyName("totalResults")] public long TotalResults { get; } // 标记构造函数用于反序列化,参数名需与JSON字段名匹配 [JsonConstructor] protected PagedResultBase(int currentPage, int resultsPerPage, int totalPages, long totalResults) { CurrentPage = currentPage > totalPages ? totalPages : currentPage; ResultsPerPage = resultsPerPage; TotalPages = totalPages; TotalResults = totalResults; } protected PagedResultBase() { } }
- 修改
PagedResult<T>类的Items属性:
[JsonPropertyName("items")] public List<T> Items { get; set; }
- 移除JSON中的
isEmpty和isNotEmpty字段——这两个值由Items推导而来,无需在JSON中存储。
方案二:使用驼峰命名策略
通过JsonSerializerOptions启用驼峰命名映射,同时处理只读属性的反序列化:
- 处理只读属性(二选一即可):
- 方式一:给
PagedResultBase的只读属性添加[JsonInclude]特性,允许反序列化只读属性:
public abstract class PagedResultBase { [JsonInclude] public int CurrentPage { get; } [JsonInclude] public int ResultsPerPage { get; } [JsonInclude] public int TotalPages { get; } [JsonInclude] public long TotalResults { get; } protected PagedResultBase() { } protected PagedResultBase(int currentPage, int resultsPerPage, int totalPages, long totalResults) { CurrentPage = currentPage > totalPages ? totalPages : currentPage; ResultsPerPage = resultsPerPage; TotalPages = totalPages; TotalResults = totalResults; } }- 方式二:确保构造函数参数名与属性名匹配,System.Text.Json会自动通过构造函数为只读属性赋值,无需额外特性。
- 方式一:给
- 修改测试代码中的反序列化逻辑:
var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; var pagedResult = JsonSerializer.Deserialize<PagedResult<UserDtoTest>>(jsonString, options);
- 调整序列化逻辑以匹配原始JSON格式(用于断言):
var serializeOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = true // 启用缩进,匹配原始JSON格式 }; var serializedJsonString = JsonSerializer.Serialize(pagedResult, serializeOptions);
- 优化断言逻辑:避免直接比较JSON字符串(格式差异会导致失败),改为比较JSON内容的等效性:
var expectedJson = JsonSerializer.Deserialize<JsonElement>(jsonString); var actualJson = JsonSerializer.Deserialize<JsonElement>(serializedJsonString); actualJson.Should().BeEquivalentTo(expectedJson);
额外注意点
务必移除JSON中的isEmpty和isNotEmpty字段,否则序列化后的JSON会因缺少这两个字段导致断言失败——这两个值属于派生属性,无需持久化存储。
内容的提问来源于stack exchange,提问作者Kirill Topchy
相关产品推荐
相关产品推荐

