5.14JSON 包装器与双重序列化

在与第三方 API 进行 HTTP 远程通信时,通常会返回统一结构的 JSON 响应,例如 ApiResult 类型,其中实际数据存放在 Data 属性中:

启用 JSON 响应反序列化包装器#

在与第三方 API 进行 HTTP 远程通信时,通常会返回统一结构的 JSON 响应,例如 ApiResult<T> 类型,其中实际数据存放在 Data 属性中:

cs
public class ApiResult<T>{    public bool Success { get; set; }    public T? Data { get; set; }    // 实际返回数据}

HTTP 声明式请求通过 JsonResponseWrapperAttribute 特性来启用 JSON 响应反序列化包装器。相应的 HTTP 声明式提取器实现为 JsonResponseWrapperDeclarativeExtractor 类型,该类型负责解析 JsonResponseWrapperAttribute 特性并构建 HttpRequestBuilder 实例所需的启用 JSON 响应反序列化包装器配置。

在未启用 JSON 响应反序列化包装器功能时,每次调用都需要显式指定 ApiResult<T> 类型:

cs
public interface IHttpService : IHttpDeclarative{    [Get("https://furion.net")]    Task<ApiResult<string>> GetStringAsync();    [Get("https://furion.net/")]    Task<ApiResult<JsonModel>> GetJsonModelAsync();}

启用方式#

1. 单次启用#

为简化调用流程,可配置 JSON 响应反序列化包装器,使其自动提取 Data 属性内容:

cs
// 配置默认 HTTP 客户端services.AddHttpClient(string.Empty)    .ConfigureOptions(options =>    {        options.JsonResponseWrapper = new JsonResponseWrapper(typeof(ApiResult<>), nameof(ApiResult<>.Data));    });

配置完成后,通过 [JsonResponseWrapper] 启用该功能,之后只需指定目标数据类型,无需重复声明 ApiResult<T>

cs
// 在接口定义上应用,影响所有方法[JsonResponseWrapper]public interface IHttpService : IHttpDeclarative{    // 默认自动应用    [Get("https://furion.net/")]    Task<string> GetStringAsync();    // 在方法上应用    [JsonResponseWrapper]  // 可显示启用(无需)    [Get("https://furion.net/")]    Task<string> GetStringAsync();    [JsonResponseWrapper(false)]   // 禁用 JSON 响应反序列化包装器,需传入完整的响应类型    [Get("https://furion.net/")]    Task<ApiResult<JsonModel>> GetJsonModelAsync();}

框架将在运行时自动创建 ApiResult<string> 实例,并返回其 Data 属性的值。

2. 全局启用(默认对所有请求生效)#

也可全局启用 JSON 响应反序列化包装器功能,只需设置 UseJsonResponseWrappertrue

cs
// 配置默认 HTTP 客户端services.AddHttpClient(string.Empty)    .ConfigureOptions(options =>    {        options.JsonResponseWrapper = new JsonResponseWrapper(typeof(ApiResult<>), nameof(ApiResult<>.Data));        options.UseJsonResponseWrapper = true;    });

全局启用后,所有请求默认使用包装功能:

cs
// [JsonResponseWrapper]   // 无需显式设置 [JsonResponseWrapper]public interface IHttpService : IHttpDeclarative{    [Get("https://furion.net/")]    Task<string> GetStringAsync();}

3. 单次禁用(覆盖全局设置)#

若需对特定请求禁用该功能,可设置 [JsonResponseWrapper(false)] 特性。

自定义结果处理(ResultHandler#

有时除了提取 Data,还需对响应做额外校验或转换。可通过 ResultHandler 回调实现:

cs
// 配置默认 HTTP 客户端services.AddHttpClient(string.Empty)    .ConfigureOptions(options =>    {        options.JsonResponseWrapper = new JsonResponseWrapper(typeof(ApiResult<>), nameof(ApiResult<>.Data))        {            ResultHandler = context =>            {                if (context.Instance is { } instance)                {                     // 可访问包装类型实例,获取其任意属性                    var success = context.GetPropertyValue<bool>(nameof(ApiResult<>.Success));                }                // 例如确保请求成功                context.ResponseMessage.EnsureSuccessStatusCode();                // 返回最终的目标结果(即 Data 的值)                return context.Result;            }        };    });

通过 ResultHandler,您可以在返回最终数据前执行任何自定义逻辑(如校验、转换或异常处理),使请求处理更加灵活。

context 参数的类型为 JsonResponseWrapperContext,包含以下属性和方法:

  • 属性

    • Instance:包装类型的具体实例(如 ApiResult<T>object? 类型)。
    • Result:目标结果(即 Data 的值,object? 类型)。
    • ResponseMessage:响应消息(HttpResponseMessage 类型)。
  • 方法

    • GetPropertyValue<T>(propertyName):获取包装类型的具体类型(即 Instance) 指定属性值。

JsonResponseWrapperAttribute 包含以下构造函数和属性:

  • 构造函数

    • new():作用于方法或接口,启用 JSON 响应反序列化包装器。
    • new(enabled):作用于方法或接口,设置是否启用 JSON 响应反序列化包装器。
  • 属性

    • Enabled:是否启用(bool 类型),默认值为 true(启用)。

响应 JSON 双重序列化处理#

在与第三方 API 进行 HTTP 远程通信时,极少数情况下可能会遇到服务器返回的 JSON 数据被意外进行了双重序列化(有时也可能是刻意为之)。例如,本应返回 "{\"id\":1,\"name\":\"furion\"}",却因双重序列化变成了 "\"{\\\"id\\\":10, \\\"name\\\":\\\"furion\\\"}\""。针对这类情况,框架提供了解包支持。

HTTP 声明式请求通过 JsonResponseStringUnwrapAttribute 特性来启用 JSON 响应内容字符串的解包处理。相应的 HTTP 声明式提取器实现为 JsonResponseStringUnwrapDeclarativeExtractor 类型,该类型负责解析 JsonResponseStringUnwrapAttribute 特性并构建 HttpRequestBuilder 实例所需的启用 JSON 响应内容字符串的解包处理配置。

cs
// 在接口定义上应用,影响所有方法[JsonResponseStringUnwrap]public interface IHttpService : IHttpDeclarative{    // 默认自动应用    [Get("https://furion.net/")]    Task<YourModel> GetAsync();    // 在方法上应用    [JsonResponseStringUnwrap]  // 可显示启用(无需)    [Get("https://furion.net/")]    Task<YourModel> GetAsync();    [JsonResponseStringUnwrap(false)]   // 禁用 JSON 响应内容字符串的解包处理    [Get("https://furion.net/")]    Task<YourModel>> GetAsync();}

若需对特定请求禁用该功能,可设置 [JsonResponseStringUnwrap(false)] 特性。

JsonResponseStringUnwrapAttribute 包含以下构造函数和属性:

  • 构造函数

    • new():作用于方法或接口,启用 JSON 响应内容字符串的解包处理。
    • new(enabled):作用于方法或接口,设置是否启用 JSON 响应内容字符串的解包处理。
  • 属性

    • Enabled:是否启用(bool 类型),默认值为 true(启用)。