.NET泛型API仓储中ReadFromJsonAsync<List<T>>反序列化带包装的API响应时抛JsonException的解决方法
.NET泛型API仓储中ReadFromJsonAsync<List>反序列化带包装的API响应时抛JsonException的解决方法
这个问题我太熟悉了——你踩的坑是典型的JSON结构不匹配导致的反序列化失败。API返回的是一个包装后的对象(根节点包含data、message、isSuccess三个属性),但你的代码直接尝试把整个响应反序列化成List<T>类型,两者的结构完全对不上,自然会抛出JsonException。
下面给你一套通用的解决方案,完美适配你的分层项目结构:
第一步:定义通用的API响应包装类
因为你的所有API返回结构都是统一的(有数据集合、消息、成功标识),我们先创建一个泛型类来对应这个结构,把它放在Core层或者Model层,方便全项目复用:
using System.Text.Json.Serialization; // 通用API响应模型,匹配返回的JSON结构 public class ApiResponse<T> { // 用JsonPropertyName显式映射JSON的小驼峰字段 [JsonPropertyName("data")] public T Data { get; set; } [JsonPropertyName("message")] public string Message { get; set; } [JsonPropertyName("isSuccess")] public bool IsSuccess { get; set; } }
这里用JsonPropertyName特性是为了严格匹配JSON的小驼峰字段,避免大小写不匹配的问题;如果你不想写特性,也可以在反序列化时开启PropertyNameCaseInsensitive = true,不过显式映射更严谨。
第二步:修改泛型仓储的反序列化逻辑
接下来要修改ApiRepositoryBase.cs里的GetListAsync方法,把反序列化的目标从List<T>改成ApiResponse<List<T>>,先解析整个API响应,再提取里面的data集合:
public async Task<IDataResult<List<T>>> GetListAsync(string endpoint, Expression<Func<T, bool>>? filter = null) { try { using var httpClient = new HttpClient(); httpClient.BaseAddress = new Uri(_baseUrl.Trim()); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token); var response = await httpClient.GetAsync($"api/{endpoint}"); if (!response.IsSuccessStatusCode) return new ErrorDataResult<List<T>>(response.ReasonPhrase ?? "Api error"); // 配置反序列化选项(如果用了JsonPropertyName,这一步可以省略) var jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; // 先反序列化成通用API响应模型 var apiResponse = await response.Content.ReadFromJsonAsync<ApiResponse<List<T>>>(jsonOptions); if (apiResponse == null) return new ErrorDataResult<List<T>>("Failed to parse API response"); // 利用API返回的IsSuccess判断请求是否真正成功 if (!apiResponse.IsSuccess) return new ErrorDataResult<List<T>>(apiResponse.Message ?? "API request failed"); // 提取核心数据集合 var data = apiResponse.Data; if (data == null) return new ErrorDataResult<List<T>>("No data returned from API"); // 原有过滤逻辑保持不变 if (filter != null) { var compiledFilter = filter.Compile(); data = data.Where(compiledFilter).ToList(); } return new SuccessDataResult<List<T>>(data); } catch (Exception ex) { return new ErrorDataResult<List<T>>(ex.Message); } }
为什么这样改能解决问题?
- 现在我们用
ApiResponse<List<T>>来匹配API返回的完整JSON结构,反序列化时就能正确解析整个响应内容,不会再因为根节点类型不匹配报错。 - 我们还能利用API返回的
IsSuccess和Message字段做更精准的错误处理,而不是只依赖HTTP状态码——比如有时候HTTP返回200,但业务逻辑上请求是失败的(比如参数错误),这时候就能通过IsSuccess判断出来。 - 这个方案是完全通用的,不管你后续加什么新的实体(比如Kitap、Yazar等),都能复用这个
ApiResponse<T>和修改后的仓储方法,不用重复写代码。
备注:内容来源于stack exchange,提问作者NEX
相关产品推荐
相关产品推荐

