5.31Enabling the JSON Response Deserialization Wrapper

Updated on Aug 20, 2026~8 min read

When performing HTTP remote communication with third-party APIs, a JSON response with a unified structure is usually returned, such as the ApiResult<T> type, where the actual data is stored in the Data property:

cs
public class ApiResult<T>{    public bool Success { get; set; }    public T? Data { get; set; }    // Actual returned data}

HTTP declarative requests enable the JSON response deserialization wrapper through the JsonResponseWrapperAttribute attribute. The corresponding HTTP declarative extractor is implemented as the JsonResponseWrapperDeclarativeExtractor type, which is responsible for parsing the JsonResponseWrapperAttribute attribute and building the JSON response deserialization wrapper configuration required by an HttpRequestBuilder instance.

When the JSON response deserialization wrapper feature is not enabled, the ApiResult<T> type must be explicitly specified on every call:

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

Ways to Enable

1. One-Time Enablement

To simplify the calling process, you can configure the JSON response deserialization wrapper so that it automatically extracts the content of the Data property:

cs
// Configure the default HTTP clientservices.AddHttpClient(string.Empty)    .ConfigureOptions(options =>    {        options.JsonResponseWrapper = new JsonResponseWrapper(typeof(ApiResult<>), nameof(ApiResult<>.Data));    });

After configuration is complete, enable the feature via [JsonResponseWrapper]; afterwards you only need to specify the target data type, without repeatedly declaring ApiResult<T>:

cs
// Applied on the interface definition, affecting all methods[JsonResponseWrapper]public interface IHttpService : IHttpDeclarative{    // Applied automatically by default    [Get("https://furion.net/")]    Task<string> GetStringAsync();    // Applied on the method    [JsonResponseWrapper]  // Can be explicitly enabled (not required)    [Get("https://furion.net/")]    Task<string> GetStringAsync();    [JsonResponseWrapper(false)]   // Disable the JSON response deserialization wrapper; the complete response type must be passed in    [Get("https://furion.net/")]    Task<ApiResult<JsonModel>> GetJsonModelAsync();}

At runtime, the framework automatically creates an ApiResult<string> instance and returns the value of its Data property.

2. Global Enablement (Enabled by Default for All Requests)

You can also globally enable the JSON response deserialization wrapper feature by simply setting UseJsonResponseWrapper to true:

cs
// Configure the default HTTP clientservices.AddHttpClient(string.Empty)    .ConfigureOptions(options =>    {        options.JsonResponseWrapper = new JsonResponseWrapper(typeof(ApiResult<>), nameof(ApiResult<>.Data));        options.UseJsonResponseWrapper = true;    });

After global enablement, all requests use the wrapper feature by default:

cs
// [JsonResponseWrapper]   // No need to explicitly set [JsonResponseWrapper]public interface IHttpService : IHttpDeclarative{    [Get("https://furion.net/")]    Task<string> GetStringAsync();}

3. One-time Disabling (Overriding Global Settings)

If you need to disable this feature for a specific request, set the [JsonResponseWrapper(false)] attribute.

Custom Result Handling (ResultHandler)

Sometimes, in addition to extracting Data, you may need to perform additional validation or conversion on the response. This can be implemented through the ResultHandler callback:

cs
// Configure the default HTTP clientservices.AddHttpClient(string.Empty)    .ConfigureOptions(options =>    {        options.JsonResponseWrapper = new JsonResponseWrapper(typeof(ApiResult<>), nameof(ApiResult<>.Data))        {            ResultHandler = context =>            {                if (context.Instance is { } instance)                {                     // Access the wrapper type instance and get any of its properties                    var success = context.GetPropertyValue<bool>(nameof(ApiResult<>.Success));                }                // For example, ensure the request succeeds                context.ResponseMessage.EnsureSuccessStatusCode();                // Return the final target result (i.e. the value of Data)                return context.Result;            }        };    });

With ResultHandler, you can execute any custom logic (such as validation, conversion, or exception handling) before returning the final data, making request processing more flexible.

The context parameter is of type JsonResponseWrapperContext and contains the following properties and methods:

  • Properties:

    • Instance: The concrete instance of the wrapper type (such as ApiResult<T>, of type object?).
    • Result: The target result (i.e. the value of Data, of type object?).
    • ResponseMessage: The response message (of type HttpResponseMessage).
  • Methods:

    • GetPropertyValue<T>(propertyName): Gets the specified property value from the concrete type of the wrapper type (i.e. Instance).

JsonResponseWrapperAttribute contains the following constructors and properties:

  • Constructors:

    • new(): Applies to methods or interfaces, enabling the JSON response deserialization wrapper.
    • new(enabled): Applies to methods or interfaces, setting whether the JSON response deserialization wrapper is enabled.
  • Properties:

    • Enabled: Whether it is enabled (bool type), defaulting to true (enabled).