Let’s say you have the following code:
// Assume 'baseUri' and 'request' are valid.
public async Task<T> ProcessRequest<T>(string baseUri, RestSharp.RestRequest request)
{
var restClient = new RestSharp.RestClient(baseUri);
var result = await restClient.ExecuteTaskAsync<T>(request);
return result.Data;
}
And the json returned by the request can’t be deserialized correctly. Say, for example, your C# type is expecting an int
and instead you’re returned the string "AR"
*. Let’s also assume we’re dealing with a rather lengthy json result. Out of the box, the only assistance RestSharp provides for tracking down the troublesome value is a generic exception:
The stack trace doesn’t provide many clues either:
Okay, so we know WHAT went wrong… sure would be nice to know WHERE it went wrong. So how do we narrow that down? As far I can tell, with RestSharp alone, you can’t. But if you configure it to use json.net for deserialization, you’ll get a much more specific error message, including exactly where to find the troublesome section of json.
Side Note: The intellisense for the RestSharp.RestRequest.JsonSerializer
property indicates that json.net is the default serializer. I think that’s incorrect. According to this readme document, RestSharp no longer uses json.net as the default serializer.
Configuring RestSharp To Deserialize Using Json.Net
The first thing to do is add json.net to your project. (I’m going to assume anyone reading this will know how to do that. But I’ll put this here, just in case.) Then you’ll need to implement RestSharp.Deserializers.IDeserializer
using json.net and instruct RestSharp to use that implementation to deserialize your json strings.
IDeserializer Example Implementation:
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using RestSharp;
using RestSharp.Deserializers;
namespace MyAwesomeProject
{
public class JsonDeserializer : IDeserializer
{
static readonly Lazy<JsonDeserializer> lazyInstance =
new Lazy<JsonDeserializer>(() => new JsonDeserializer());
readonly JsonSerializerSettings settings;
public static JsonDeserializer Default
{
get { return lazyInstance.Value; }
}
public JsonDeserializer()
{
settings = new JsonSerializerSettings
{
Error = ErrorHandler
};
}
public T Deserialize<T>(IRestResponse response)
{
return JsonConvert.DeserializeObject<T>(response.Content, settings);
}
void ErrorHandler(object sender, ErrorEventArgs args)
{
// Only handle initial error
if (args.CurrentObject != args.ErrorContext.OriginalObject)
return;
// This was for *my* specific problem. It is not a generic solution!
if (args.ErrorContext.Member.ToString() == "Qty" &&
args.ErrorContext.Error.Message.Contains("Could not convert string to integer"))
args.ErrorContext.Handled = true; // Leave null
}
/// <summary>
/// Not used for Json serialization
/// </summary>
string IDeserializer.RootElement { get; set; }
/// <summary>
/// Not used for Json serialization
/// </summary>
string IDeserializer.Namespace { get; set; }
/// <summary>
/// Not used for Json serialization
/// </summary>
string IDeserializer.DateFormat { get; set; }
}
}
This implementation is a singleton, backed by a static, lazy JsonDeserializer
instance, which made the most sense for my project, but is absolutely not required and can be disregarded if you choose. The important bits to take note of are the Deserialize()
method, the creation of JsonSerializerSettings
, and the error handler. This setup ensures that when json.net fails to deserialize a particular IRestResponse
, the ErrorHandler
method is invoked. As indicated in the code sample, that handler was very specific to my particular problem. Unless you also happen to have an issue with integer properties named Qty
containing non-numeric text, you’ll want to write your own handler. However, you will probably want to do something similar to what I have on the first two lines of the ErrorHandler
method because, straight from the horse’s mouth (emphasis mine):
…an unhandled error will bubble up and raise the event on each of its parents. For example an unhandled error when serializing a collection of objects will be raised twice, once against the object and then again on the collection. This will let you handle an error either where it occurred or on one of its parents.
In other words, unless you need to handle the same error potentially multiple times, you’ll want to do something similar to either my approach or the example in the json.net documentation. Everything else in my IDeserializer
, I think, is fairly self-explanatory.
The last thing you have to do is configure your RestSharp client to use your IDeserializer
, like so:
Adding RestSharp Client Handlers:
// Assume 'baseUri' and 'request' are valid.
public async Task<T> ProcessRequest<T>(string baseUri, RestSharp.RestRequest request)
{
var restClient = new RestSharp.RestClient(baseUri);
restClient.AddHandler("application/json", JsonDeserializer.Default);
restClient.AddHandler("text/json", JsonDeserializer.Default);
restClient.AddHandler("image/gif", JsonDeserializer.Default);
var result = await restClient.ExecuteTaskAsync<T>(request);
return result.Data;
}
Now when a deserialization error is encountered, you’ll get far greater detail about what the problem is:
We get a specific error message, specific C# type property, and specific line and position location within the json string. That’s more like it!
Notes
* If that seems like an awfully specific example, it’s because that’s the exact situation I encountered.