How to Deserialize JSON into C# objects correctly? - Stack Overflow

admin2025-05-02  1

I have a function that calls API requests, and I want to have each request return its own data type according to its purpose. For that, I created a class called ApiResponse. Here is its code:

    public class ApiResponse<T>
    {
        public bool Success { get; set; }
        public string? ErrorMessage { get; set; }
        public T? Data { get; set; }

        public ApiResponse()
        {
            Success = true; // Default to success
        }
    }

The way I use this class is when I request DB queries, the data, which is of type T will be DBResponse. Here is its implementation:

    public class DBResponse<T>
    {
        public int Code { get; set; }
        public string Message { get; set; }
        public bool Success { get; set; }
        public T? Data { get; set; }

        public DBResponse(int code, string message, bool success, T? data = default)
        {
            Code = code;
            Message = message;
            Success = success;
            Data = data;
        }
    }

And, the method I created for calling the API requests is:

public async Task<ApiResponse<T>> SendRequestAsync<T>(string route, object? body = null)
{
    var appSettings = new AppSettings();
    try
    {
        // Prepare the request payload
        var requestPayload = new
        {
            Database = "my-database",
            Username = "my-user",
            Password = "my-password",
            Body = body
        };

        var content = new StringContent(JsonSerializer.Serialize(requestPayload), Encoding.UTF8, "application/json");

        // Call the API
        var response = await httpClient.PostAsync($"{appSettings.ApplicationUrl}/request/{route}", content);

        if (response.IsSuccessStatusCode)
        {
            // Get the raw response JSON as a string
            var rawResponseJson = await response.Content.ReadAsStringAsync();

            try
            {
                // Deserialize the raw response JSON into ApiResponse<T>
                var apiResponse = JsonSerializer.Deserialize<ApiResponse<T>>(rawResponseJson, new JsonSerializerOptions
                {
                    PropertyNameCaseInsensitive = true
                });

                if (apiResponse == null)
                {
                    return new ApiResponse<T>
                    {
                        Success = false,
                        ErrorMessage = "No data returned from the API."
                    };
                }

                return apiResponse;
            }
            catch (Exception ex)
            {
                return new ApiResponse<T>
                {
                    Success = false,
                    ErrorMessage = $"Error deserializing response to type {typeof(T).Name}: {ex.Message}"
                };
            }
        }
        else
        {
            return new ApiResponse<T>
            {
                Success = false,
                ErrorMessage = $"HTTP Error: {response.StatusCode} - {response.ReasonPhrase}"
            };
        }
    }
    catch (Exception ex)
    {
        return new ApiResponse<T>
        {
            Success = false,
            ErrorMessage = $"SendRequestAsync general error: {ex.Message}"
        };
    }
}

I have one API request that fetches records from the database, hence its return type is ApiResponse<DBResponse<List<Dictionary<string, object>>>>. In this case, the function works well. However, when I am doing an update, insert or delete SQL commands, the return type of the call is: ApiResponse<DBResponse<int>>. In this case, I am getting an error on the command:

var apiResponse = JsonSerializer.Deserialize<ApiResponse<T>>(rawResponseJson, new JsonSerializerOptions
                {
                    PropertyNameCaseInsensitive = true
                });

Can someone please help me understand the problem I am having with the Deserialize call? Why can't it convert simpler types from JSON? I tried calling the Deserialize call without the PropertyNameCaseInsensitive = true, but that is worse. Here is an example for JSON that works well:

{"success":true,"errorMessage":"","data":{"code":0,"message":"","success":true,"data":[{"Id":5,"Name":"One Value Updated Value Updated Value","CreatedOn":"2025-01-02T04:44:40"}]}}

And, here is an example for JSON that fails:

{"success":true,"errorMessage":"","data":{"code":0,"message":"Update successful.","success":true,"data":1}}

With this error message:

System.Text.Json.JsonException
  HResult=0x80131500
  Message=The JSON value could not be converted to Project.SharedLibrary.DBResponse`1[System.Int32]. Path: $.data.data | LineNumber: 0 | BytePositionInLine: 105.

I have a function that calls API requests, and I want to have each request return its own data type according to its purpose. For that, I created a class called ApiResponse. Here is its code:

    public class ApiResponse<T>
    {
        public bool Success { get; set; }
        public string? ErrorMessage { get; set; }
        public T? Data { get; set; }

        public ApiResponse()
        {
            Success = true; // Default to success
        }
    }

The way I use this class is when I request DB queries, the data, which is of type T will be DBResponse. Here is its implementation:

    public class DBResponse<T>
    {
        public int Code { get; set; }
        public string Message { get; set; }
        public bool Success { get; set; }
        public T? Data { get; set; }

        public DBResponse(int code, string message, bool success, T? data = default)
        {
            Code = code;
            Message = message;
            Success = success;
            Data = data;
        }
    }

And, the method I created for calling the API requests is:

public async Task<ApiResponse<T>> SendRequestAsync<T>(string route, object? body = null)
{
    var appSettings = new AppSettings();
    try
    {
        // Prepare the request payload
        var requestPayload = new
        {
            Database = "my-database",
            Username = "my-user",
            Password = "my-password",
            Body = body
        };

        var content = new StringContent(JsonSerializer.Serialize(requestPayload), Encoding.UTF8, "application/json");

        // Call the API
        var response = await httpClient.PostAsync($"{appSettings.ApplicationUrl}/request/{route}", content);

        if (response.IsSuccessStatusCode)
        {
            // Get the raw response JSON as a string
            var rawResponseJson = await response.Content.ReadAsStringAsync();

            try
            {
                // Deserialize the raw response JSON into ApiResponse<T>
                var apiResponse = JsonSerializer.Deserialize<ApiResponse<T>>(rawResponseJson, new JsonSerializerOptions
                {
                    PropertyNameCaseInsensitive = true
                });

                if (apiResponse == null)
                {
                    return new ApiResponse<T>
                    {
                        Success = false,
                        ErrorMessage = "No data returned from the API."
                    };
                }

                return apiResponse;
            }
            catch (Exception ex)
            {
                return new ApiResponse<T>
                {
                    Success = false,
                    ErrorMessage = $"Error deserializing response to type {typeof(T).Name}: {ex.Message}"
                };
            }
        }
        else
        {
            return new ApiResponse<T>
            {
                Success = false,
                ErrorMessage = $"HTTP Error: {response.StatusCode} - {response.ReasonPhrase}"
            };
        }
    }
    catch (Exception ex)
    {
        return new ApiResponse<T>
        {
            Success = false,
            ErrorMessage = $"SendRequestAsync general error: {ex.Message}"
        };
    }
}

I have one API request that fetches records from the database, hence its return type is ApiResponse<DBResponse<List<Dictionary<string, object>>>>. In this case, the function works well. However, when I am doing an update, insert or delete SQL commands, the return type of the call is: ApiResponse<DBResponse<int>>. In this case, I am getting an error on the command:

var apiResponse = JsonSerializer.Deserialize<ApiResponse<T>>(rawResponseJson, new JsonSerializerOptions
                {
                    PropertyNameCaseInsensitive = true
                });

Can someone please help me understand the problem I am having with the Deserialize call? Why can't it convert simpler types from JSON? I tried calling the Deserialize call without the PropertyNameCaseInsensitive = true, but that is worse. Here is an example for JSON that works well:

{"success":true,"errorMessage":"","data":{"code":0,"message":"","success":true,"data":[{"Id":5,"Name":"One Value Updated Value Updated Value","CreatedOn":"2025-01-02T04:44:40"}]}}

And, here is an example for JSON that fails:

{"success":true,"errorMessage":"","data":{"code":0,"message":"Update successful.","success":true,"data":1}}

With this error message:

System.Text.Json.JsonException
  HResult=0x80131500
  Message=The JSON value could not be converted to Project.SharedLibrary.DBResponse`1[System.Int32]. Path: $.data.data | LineNumber: 0 | BytePositionInLine: 105.
Share Improve this question edited Jan 2 at 5:31 Avraham Cohen asked Jan 2 at 5:11 Avraham CohenAvraham Cohen 951 silver badge8 bronze badges 6
  • Can you share the complete error message and the caller for the scenario the return type of the call is: ApiResponse<DBResponse<int>>. Also, please share the response returned in the question. – Yong Shun Commented Jan 2 at 5:20
  • The JSON for the ApiResponse<DBResponse<int>> is: {"success":true,"errorMessage":"","data":{"code":0,"message":"Update successful.","success":true,"data":1}}. The error message is: System.Text.Json.JsonException HResult=0x80131500 Message=The JSON value could not be converted to Project.SharedLibrary.DBResponse`1[System.Int32]. Path: $.data.data | LineNumber: 0 | BytePositionInLine: 105. – Avraham Cohen Commented Jan 2 at 5:28
  • @YongShun I edited the post to include 2 JSONs. One that works okay, and the second that fails, followed by the error message. – Avraham Cohen Commented Jan 2 at 5:32
  • Test: Try changing to: public T Data { get; set; } (not Nullable) and see how that goes. – Poul Bak Commented Jan 2 at 5:46
  • 1 Sounds odd, are you calling with SendRequestAsync<DBResponse<int>>(...);? It works in this demo. – Yong Shun Commented Jan 2 at 5:48
 |  Show 1 more comment

1 Answer 1

Reset to default 0

Well, I played a lot with this with no luck. But, eventually, I resolved my problem by changing the response class to not use a generic type. I also, for simplicity, merged the 2 response classes (ApiResponse & DBResponse) into one Response class whose definition is:

public class Response
{
    public bool Success { get; set; }

    public string Message { get; set; }

    public List<Dictionary<string, object>> Data { get; set; }

    public Response()
    {
        Success = true;
    }
}

And, for reading that, I used the simple function:

var content = new StringContent(JsonSerializer.Serialize(requestPayload), Encoding.UTF8, "application/json");

// Call the API
var response = await httpClient.PostAsync($"{appSettings.ApplicationUrl}/request/{route}", content);

if (response.IsSuccessStatusCode)
{
    try
    {
        var apiResponse = await response.Content.ReadFromJsonAsync<Response>();
        return apiResponse;
    }
    catch (Exception ex)
    {
        return new Response($"Error deserializing response: {ex.Message}");
    }
}
else
{
    return new Response($"HTTP Error: {response.StatusCode} - {response.ReasonPhrase}");
}
转载请注明原文地址:http://www.anycun.com/QandA/1746133333a92035.html