Reputation: 1312
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:
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
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 json.net you would have additional options. E.g. you could:
Plug Json.NET into RestSharp as shown in Custom JSON Serializer and JSON Deserializer for RestSharp or RestSharp serialization to JSON, object is not using SerializeAs attribute as expected;
Then use a JsonConverter
to select the correct polymorphic subtype for PostalAddressOccupancy
as shown in e.g. JsonConverter with Interface or Deserializing polymorphic json classes without type information using json.net.
Upvotes: 1