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:
{
\"er
A slightly modified version of @Yuval's answer.
static T TryParse<T>(string jsonData) where T : new()
{
JSchemaGenerator generator = new JSchemaGenerator();
JSchema parsedSchema = generator.Generate(typeof(T));
JObject jObject = JObject.Parse(jsonData);
return jObject.IsValid(parsedSchema) ?
JsonConvert.DeserializeObject<T>(jsonData) : default(T);
}
This can be used when you don't have the schema as text readily available for any type.
You may deserialize JSON to a dynamic, and check whether the root element is error
. Note that you probably don't have to check for the presence of status
and code
, like you actually do, unless the server also sends valid non-error responses inside a error
node.
Aside that, I don't think you can do better than a try/catch
.
What actually stinks is that the server sends an HTTP 200 to indicate an error. try/catch
appears simply as checking of inputs.
Just to provide an example of the try/catch approach (it may be useful to somebody).
public static bool TryParseJson<T>(this string obj, out T result)
{
try
{
// Validate missing fields of object
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.MissingMemberHandling = MissingMemberHandling.Error;
result = JsonConvert.DeserializeObject<T>(obj, settings);
return true;
}
catch (Exception)
{
result = default(T);
return false;
}
}
Then, it can be used like this:
var result = default(MyObject);
bool isValidObject = jsonString.TryParseJson<MyObject>(out result);
if(isValidObject)
{
// Do something
}
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
@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…
}
To test whether a text is valid JSON regardless of schema, you could also do a check on the number of quotation marks:" in your string response, as shown below :
// Invalid JSON
var responseContent = "asgdg";
// var responseContent = "{ \"ip\" = \"11.161.195.10\" }";
// Valid JSON, uncomment to test these
// var responseContent = "{ \"ip\": \"11.161.195.10\", \"city\": \"York\", \"region\": \"Ontartio\", \"country\": \"IN\", \"loc\": \"-43.7334,79.3329\", \"postal\": \"M1C\", \"org\": \"AS577 Bell Afgh\", \"readme\": \"https://ipinfo.io/missingauth\"}";
// var responseContent = "\"asfasf\"";
// var responseContent = "{}";
int count = 0;
foreach (char c in responseContent)
if (c == '\"') count++; // Escape character needed to display quotation
if (count >= 2 || responseContent == "{}")
{
// Valid Json
try {
JToken parsedJson = JToken.Parse(responseContent);
Console.WriteLine("RESPONSE: Json- " + parsedJson.ToString(Formatting.Indented));
}
catch(Exception ex){
Console.WriteLine("RESPONSE: InvalidJson- " + responseContent);
}
}
else
Console.WriteLine("RESPONSE: InvalidJson- " + responseContent);