Home > Software engineering >  JSON - Generic deserialization based on object type
JSON - Generic deserialization based on object type

Time:09-23

I'm trying to implement a generic HTTP service that performs requests to my OData API based on the type of object that gets passed onto the method. For example: if I pass an object of type Employee, it will retrieve all employees, and if I pass an object of type Customer, it will retrieve all customers.

This is an example API response that returns a list of all employees:

{
    "@odata.context": "https://localhost:44375/odata/$metadata#Employees",
    "@odata.count": 3,
    "value": [
        {
            "EmployeeType": "Manager",
            "FirstName": "Test",
            "LastName": "Test",
            "Address": "Test",
            "PostalCode": "1234AB",
            "City": "Test",
            "PhoneNumber": "0641615077",
            "EmailAddress": "[email protected]",
            "HostelId": 1,
            "Id": 1,
            "DateCreated": "0001-01-01T00:00:00Z",
            "LastModifiedDate": "0001-01-01T00:00:00Z"
        },
        ...
     ]
}

I want to deserialize both the count and values into an object called EntityListResponse.

    public class EntityListResponse
    {
        [System.Text.Json.Serialization.JsonPropertyName("value")]
        public List<object> Records { get; set; }
        
        [System.Text.Json.Serialization.JsonPropertyName("@odata.count")]
        public int Total { get; set; }
    }

I have built my own Blazor component which receives an object of any type as a parameter:

    [Parameter]
    public TItem Entity { get; set; }

The component has a method called "ReadEntity" which calls my generic HTTP service. The first parameter is needed for OData requests, the second parameter is the object - Employee for example.

    private async Task ReadEntity(GridReadEventArgs args)
    {
        var data = await _entityService.GetAll(args.Request, Entity);

        ...
    }

The GetAll method in the HTTP service:

        public async Task<EntityListResponse> GetAll(DataSourceRequest request, object entity)
        {
            var baseUrl = $"{entity}s?";

            var requestUrl = $"{baseUrl}{request.ToODataString()}";

            var requestMessage = new HttpRequestMessage(HttpMethod.Get, requestUrl);
            requestMessage.Headers.Add("Accept", "application/json");
            var client = HttpClient.CreateClient("IndiciaStageApi");
            var response = await client.SendAsync(requestMessage);

            if (response.IsSuccessStatusCode)
            {
                var body = await response.Content.ReadAsStringAsync();

                var options = new JsonSerializerOptions();
                options.Converters.Add(new JsonStringEnumConverter());
                var oDataResponse = JsonSerializer.Deserialize<EntityListResponse>(body, options);

                return oDataResponse;
            }
            else
            {
                throw new HttpRequestException(
                    "Request failed. I need better error handling, e.g. returning empty data.");
            }
        }

Everything in the method works perfectly except for the deserialization. The problem is that I have no idea how to let the deserializer know of which type the Records property of EntityListResponse should be. Now it just creates a list of JSON objects:

enter image description here

I would like the object type of the list to be determined based on the object type that gets passed on. So if I pass an object of type Employee, it should deserialize the response body into List, if I pass an object of type Customer it should deserialize into List, and so on.

Is this even possible to begin with?

CodePudding user response:

You can use this but make your method accept generic type parameter instead (https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/types/generics).

So your method could be something like:

async Task<T> GET<T>(string endPoint).

Then when you what to Deserialize, use the T there.

var oDataResponse = JsonSerializer.Deserialize<T>(body, options);

CodePudding user response:

Use a Generic class:

public class OdataResponce<T>
{
    [JsonPropertyName(name: "@odata.context")]
    public string ContextUrl { get; set; }

    [JsonPropertyName(name: "@odata.count")]
    public int Count { get; set; }

    [JsonPropertyName(name: "value")]
    public IEnumerable<T> Data { get; set; }
}

Usage:

var odataResponce = 
    await http.GetFromJsonAsync<OdataResponce<WeatherForecast>>(query);
  • Related