When I send a request to a service (that I do not own), it may respond either with the JSON data requested, or with an error that looks like this:
{ "error": { "status": "error message", "code": "999" } }
In both cases the HTTP response code is 200 OK, so I cannot use that to determine whether there is an error or not - I have to deserialize the response to check. So I have something that looks like this:
bool TryParseResponseToError(string jsonResponse, out Error error) { // Check expected error keywords presence // before try clause to avoid catch performance drawbacks if (jsonResponse.Contains("error") && jsonResponse.Contains("status") && jsonResponse.Contains("code")) { try { error = new JsonSerializer<Error>().DeserializeFromString(jsonResponse); return true; } catch { // The JSON response seemed to be an error, but failed to deserialize. // Or, it may be a successful JSON response: do nothing. } } error = null; return false; }
Here, I have an empty catch clause that may be in the standard execution path, which is a bad smell... Well, more than a bad smell: it stinks.
Do you know a better way to "TryParse" the response in order to avoid a catch in the standard execution path ?
Thanks to Yuval Itzchakov's answer I improved my method like that :
bool TryParseResponse(string jsonResponse, out Error error) { // Check expected error keywords presence : if (!jsonResponse.Contains("error") || !jsonResponse.Contains("status") || !jsonResponse.Contains("code")) { error = null; return false; } // Check json schema : const string errorJsonSchema = @"{ 'type': 'object', 'properties': { 'error': {'type':'object'}, 'status': {'type': 'string'}, 'code': {'type': 'string'} }, 'additionalProperties': false }"; JsonSchema schema = JsonSchema.Parse(errorJsonSchema); JObject jsonObject = JObject.Parse(jsonResponse); if (!jsonObject.IsValid(schema)) { error = null; return false; } // Try to deserialize : try { error = new JsonSerializer<Error>.DeserializeFromString(jsonResponse); return true; } catch { // The JSON response seemed to be an error, but failed to deserialize. // This case should not occur... error = null; return false; } }
I kept the catch clause... just in case.
A common way to deserialize JSON is to first create a class with properties and fields that represent one or more of the JSON properties. Then, to deserialize from a string or a file, call the JsonSerializer. Deserialize method.
Newtonsoft. Json uses reflection to get constructor parameters and then tries to find closest match by name of these constructor parameters to object's properties. It also checks type of property and parameters to match. If there is no match found, then default value will be passed to this parameterized constructor.
For deserializing the responses, we create a separate class that has the same variables that are present in the JSON response like StatusCode and Message. In the code, we can use this class object to read the JSON response as shown in the example above.
There is no polymorphic deserialization (equivalent to Newtonsoft. Json's TypeNameHandling ) support built-in to System.
@Victor LG's answer using Newtonsoft is close, but it doesn't technically avoid the a catch as the original poster requested. It just moves it elsewhere. Also, though it creates a settings instance to enable catching missing members, those settings aren't passed to the DeserializeObject call so they are actually ignored.
Here's a "catch free" version of his extension method that also includes the missing members flag. The key to avoiding the catch is setting the Error
property of the settings object to a lambda which then sets a flag to indicate failure and clears the error so it doesn't cause an exception.
public static bool TryParseJson<T>(this string @this, out T result) { bool success = true; var settings = new JsonSerializerSettings { Error = (sender, args) => { success = false; args.ErrorContext.Handled = true; }, MissingMemberHandling = MissingMemberHandling.Error }; result = JsonConvert.DeserializeObject<T>(@this, settings); return success; }
Here's an example to use it:
if(value.TryParseJson(out MyType result)) { // Do something with result… }
With Json.NET
you can validate your json against a schema:
string schemaJson = @"{ 'status': {'type': 'string'}, 'error': {'type': 'string'}, 'code': {'type': 'string'} }"; JsonSchema schema = JsonSchema.Parse(schemaJson); JObject jobj = JObject.Parse(yourJsonHere); if (jobj.IsValid(schema)) { // Do stuff }
And then use that inside a TryParse method.
public static T TryParseJson<T>(this string json, string schema) where T : new() { JsonSchema parsedSchema = JsonSchema.Parse(schema); JObject jObject = JObject.Parse(json); return jObject.IsValid(parsedSchema) ? JsonConvert.DeserializeObject<T>(json) : default(T); }
Then do:
var myType = myJsonString.TryParseJson<AwsomeType>(schema);
Please note that schema validation is no longer part of the main Newtonsoft.Json package, you'll need to add the Newtonsoft.Json.Schema package.
As noted in the comments, "JSONSchema" have a pricing model, meaning it isn't free. You can find all the information here
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With