Paul Bambury
Paul Bambury

Reputation: 1312

Deserializing REST resource to generic object

I am writing a program to integrate data between our Student Record System and the Finance System. The SRS is a cloud based solution while the Finance system is on-prem. Data is to be extracted from the SRS using its RESTful API. I then plan to write the returned resources to a staging database for integration with the finance system. After a bit of research I decided to use RestSharp as a foundation for my solution.

Following the Recommended Usage guide I have been able to get a solution that is successfully making requests and deserializing the response data into my classes. However I’ve hit a small speed bump and need a bit of help to get going again.

The SRS Rest API contains the resource PostalAddressOccupancy. According to the SRS API documentation it has following structure:

enter image description here

My problem is with the occupant property. This, as indicated by the mixed type, can be any one of a number other resource types (ie Student, Staff, Guardian etc).

I can easily represent this in my data model using generics. Eg

public class PostalAddressOccupancy<T>
{
    public DateTime EffectiveDate { get; set; }
    public DateTime EndDate { get; set; }
    public string EntityType { get; set; }
    public string Href { get; set; }
    public string Id { get; set; }
    public bool IsCorrespondenceAddress { get; set; }
    public T Occupant { get; set; }
    public PostalAddress PostalAddress { get; set; }
    public string PostalAddressType { get; set; }

    public static PostalAddressOccupancy<T> GetPostalAddressOccupancy(string id)
    {
        var request = new RestRequest
        {
            Resource = "PostalAddressOccupancy/{Id}",
            RootElement = "postalAddressOccupancy"
        };

        request.AddParameter("Id", id, ParameterType.UrlSegment);

        RestApiClient restApiClient = new RestApiClient("SRS API");
        return restApiClient.Execute<PostalAddressOccupancy<T>>(request);
    }

    public static List<PostalAddressOccupancy<T>> GetPostalAddressOccupancies()
    {
        List<PostalAddressOccupancy<T>> list = new List<PostalAddressOccupancy<T>>();

        var request = new RestRequest
        {
            Resource = "PostalAddressOccupancy",
            RootElement = "postalAddressOccupancies"
        };

        RestApiClient restApiClient = new RestApiClient("SRS API");

        foreach (var pao in restApiClient.Execute<List<PostalAddressOccupancy<T>>>(request))
        {
            list.Add(GetPostalAddressOccupancy(pao.Href.Split('/').Last()));
        }

        return list;
    }
}

My problem is how to get RestSharp to correctly understand which type of occupant is being returned in the response data. Using the GetPostalAddressOccupancies method above I get a list of objects with everything correctly deserialized apart from the occupant. This is returned as a base object type with its properties contained as key/value pairs.

Do I need to decorate my class or generic property with some tags to help provide a hint to the RestSharp deserializers?

I've also noticed that resources include base properties (eg entityType). Thus an occupant type can be identified through this, but I am unsure how that may help.

Upvotes: 1

Views: 1950

Answers (1)

dbc
dbc

Reputation: 116515

Under the hood, RestSharp uses SimpleJson for JSON serialization. Unlike Json.NET this serializer doesn't have built-in support for polymorphic property deserialization, custom converters or surrogate replacement.

Thus what you're going to need to do is to parse your response into an intermediate object -- specifically JsonObject -- then identify the type of occupant based on the properties present, then deserialize to a final type.

Firstly, it's going to be much easier to extract a non-generic base class for PostalAddressOccupancy<T> like so:

public abstract class PostalAddressOccupancy
{
    public DateTime EffectiveDate { get; set; }
    public DateTime EndDate { get; set; }
    public string EntityType { get; set; }
    public string Href { get; set; }
    public string Id { get; set; }
    public bool IsCorrespondenceAddress { get; set; }
    public PostalAddress PostalAddress { get; set; }
    public string PostalAddressType { get; set; }

    public abstract object GetOccupant();
}

public class PostalAddressOccupancy<T> : PostalAddressOccupancy
{
    public T Occupant { get; set; }

    public override object GetOccupant()
    {
        return Occupant;
    }
}

public class PostalAddress
{
    // Or whatever.  Type was not included in the question.
    public string Address { get; set; }
}

Now imagine you have a variety of occupant types including:

public class Student
{
    public string Name { get; set; }
    public string StudentId { get; set; }
}

public class Staff
{
    public string Name { get; set; }
    public string StaffId { get; set; }
}

Then you can deserialize to a PostalAddressOccupancy<T> given a IRestResponse where T could be Student or Staff using the following method that looks at the property names actually present in the JSON and selects the appropriate final type:

using SimpleJson;
using RestSharp;

public static class PostalAddressOccupancyExtensions
{
    public static PostalAddressOccupancy DeserializePostalAddressOccupancy(this RestSharp.IRestResponse response)
    {
        var addressObj = (JsonObject)SimpleJson.SimpleJson.DeserializeObject(response.Content);
        var type = PostalAddressOccupancyExtensions.InferPostalAddressOccupancyType(addressObj);
        return (PostalAddressOccupancy)SimpleJson.SimpleJson.DeserializeObject(response.Content, type);
    }

    static Type InferPostalAddressOccupancyType(JsonObject root)
    {
        var occupantObj = root["Occupant"];
        if (occupantObj is JsonObject)
        {
            var occupant = (JsonObject)occupantObj;
            // Add logic to recognize other cases as required.
            if (occupant.ContainsKey("StudentId"))
            {
                return typeof(PostalAddressOccupancy<Student>);
            }
            else if (occupant.ContainsKey("StaffId"))
            {
                return typeof(PostalAddressOccupancy<Staff>);
            }
        }
        // Or throw an exception, if you prefer.
        return typeof(PostalAddressOccupancy<>).MakeGenericType(new[] { occupantObj.GetType() });
    }
}

Notice that having a non-generic base type means that the return signature can be something other than object. Sample working .Net fiddle.

Note that if you were willing to use you would have additional options. E.g. you could:

Upvotes: 1

Related Questions