当API响应包含包装对象时,.NET的 ReadFromJsonAsync>() 会抛出JsonException

后端开发 2026-07-12

我正在开发一个带有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导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章