当API响应包含包装对象时,.NET的 ReadFromJsonAsync>() 会抛出JsonException
我正在开发一个带有API、BLL、DAL和 UI层的分层.NET项目。
UI层通过一个用 HttpClient 构建的通用API仓储来调用API。
我的项目结构大致如下:
Core
└─ DataAccess
└─ Repository
└─ ApiRepositoryBase.cs
Kutuphane.API
└─ Controllers
└─ DilController.cs
Kutuphane.Model
└─ Entity
└─ Dil.cs
Kutuphane.UI
└─ Services
API返回以下JSON:
{
"data": [
{
"dilId": 1,
"dilAdi": "Türkçe",
"dilKodu": "tr",
"aktifMi": true
},
{
"dilId": 2,
"dilAdi": "İngilizce",
"dilKodu": "en",
"aktifMi": true
}
],
"message": null,
"isSuccess": true
}
实体:
public class Dil : IEntity
{
public short DilId { get; set; }
public string DilAdi { get; set; }
public string DilKodu { get; set; }
public bool AktifMi { get; set; } = true;
}
通用API仓储方法:
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");
var data = await response.Content.ReadFromJsonAsync<List<T>>();
if (data == null)
return new ErrorDataResult<List<T>>("Deserialization failed");
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);
}
}
我收到了这个异常:
System.Text.Json.JsonException:
JSON值无法转换为 System.Collections.Generic.List。
路径: $ | 行号: 0 | 行内字节位置: 1.
异常发生在这行:
var data = await response.Content.ReadFromJsonAsync<List<T>>();
问题
由于API的响应在一个 data 属性中包含列表,而不是作为根JSON数组,请在通用仓储场景中以正确的方式对这个响应进行反序列化?
解决方案
解决方案取决于其他端点是否也使用包装器。
如果所有端点都使用包装器,我会在代码中定义它,并在仓储中使用它
public class ApiResopnseWrapper<T>
{
public List<T> Data { get; set; }
public string Message { get; set; }
public bool IsSuccess { get; set; }
}
并把你的方法改为使用这个类:
public async Task<IDataResult<List<T>>> GetListAsync(string endpoint, Expression<Func<T, bool>>? filter = null)
{
...
var wrapper = await response.Content.ReadFromJsonAsync<ApiResopnseWrapper<T>>();
var data = wrapper.Data;
...
}
但如果API不是所有响应都通过包装器返回(有些是原始列表),那么需要进一步调整。
如果你知道哪些端点返回包装器,哪些不是,你可以通过在方法中添加一个标志来处理:
public async Task<IDataResult<List<T>>> GetListAsync(
string endpoint,
bool hasWrapper,
Expression<Func<T, bool>>? filter = null)
{
...
var data = hasWrapper
? (await response.Content.ReadFromJsonAsync<ApiResopnseWrapper<T>>())
.Data
: await response.Content.ReadFromJsonAsync<List<T>>();
...
}
另一种选择是你对API可能返回的内容完全一无所知,这种情况可能在你控制之外发生变化,并且你想让实现更加健壮,那么我会定义下面的辅助工具:
private static List<T> GetDataAsync<T>(HttpResponseMessage response, CancellationToken cancellationToken)
{
try
{
return await response.Content.ReadFromJsonAsync<List<T>>(cancellationToken);
}
catch (JsonException ex)
{
return (await response.Content.ReadFromJsonAsync<ApiResopnseWrapper<T>>(cancellationToken))
.Data;
}
}
然后在方法中使用它:
public async Task<IDataResult<List<T>>> GetListAsync(string endpoint, Expression<Func<T, bool>>? filter = null)
{
...
var data = await GetDataAsync(response, cancellationToken: default);
...
}
你甚至可以改进上述解决方案,检查JSON表示的是数组还是对象并据此进行反序列化,而不是使用try-catch块。
注:我的建议也是尽量使用数组,而不是对它进行包装,例如 List,除非你需要该类型所暴露的特性,而该数组本身没有这些特性。不过我通常发现自己和同事们在很多地方过度使用 List,而大多数情况下其实并不需要。
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。