Deserialize json de una manera “TryParse”

Cuando envío una solicitud a un servicio (que no soy de mi propiedad), puede responder con los datos JSON solicitados o con un error que se ve así:

{ "error": { "status": "error message", "code": "999" } } 

En ambos casos, el código de respuesta HTTP es 200 OK, por lo que no puedo usar eso para determinar si hay un error o no. Tengo que deserializar la respuesta para verificar. Entonces tengo algo que se ve así:

 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().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; } 

Aquí, tengo una cláusula de captura vacía que puede estar en la ruta de ejecución estándar, que es un mal olor … Bueno, más que un mal olor: apesta.

¿Conoces una mejor manera de “probar” la respuesta para evitar una trampa en la ruta de ejecución estándar?

[EDITAR]

Gracias a la respuesta de Yuval Itzchakov , mejoré mi método así:

 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.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; } } 

Mantuve la cláusula de captura … por las dudas.

Con Json.NET puedes validar tu json contra un esquema:

  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 } 

Y luego usar eso dentro de un método TryParse.

 public static T TryParseJson(this string json, string schema) where T : new() { JsonSchema parsedSchema = JsonSchema.Parse(schema); JObject jObject = JObject.Parse(json); return jObject.IsValid(parsedSchema) ? JsonConvert.DeserializeObject(json) : default(T); } 

Entonces hazlo:

 var myType = myJsonString.TryParseJson(schema); 

Actualizar:

Tenga en cuenta que la validación del esquema ya no forma parte del paquete principal Newtonsoft.Json, deberá agregar el paquete Newtonsoft.Json.Schema .

Actualización 2:

Como se señaló en los comentarios, “JSONSchema” tiene un modelo de fijación de precios, lo que significa que no es gratuito . Puedes encontrar toda la información aquí

Una versión ligeramente modificada de la respuesta de @ Yuval.

 static T TryParse(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(jsonData) : default(T); } 

Esto se puede usar cuando no tiene el esquema como texto disponible para cualquier tipo.

Puede deserializar JSON a una dynamic y verificar si el elemento raíz es un error . Tenga en cuenta que probablemente no tenga que verificar la presencia de status y code , como lo hace en realidad, a menos que el servidor también envíe respuestas válidas que no sean de error dentro de un nodo de error .

Aparte de eso, no creo que puedas hacer algo mejor que try/catch .

Lo que realmente apesta es que el servidor envía un HTTP 200 para indicar un error. try/catch aparece simplemente como comprobación de entradas.

Solo para proporcionar un ejemplo del enfoque try / catch (puede ser útil para alguien).

 public static bool TryParseJson(this string obj, out T result) { try { // Validate missing fields of object JsonSerializerSettings settings = new JsonSerializerSettings(); settings.MissingMemberHandling = MissingMemberHandling.Error; result = JsonConvert.DeserializeObject(obj); return true; } catch (JsonSerializationException ex) { result = default(T); return false; } } 

Entonces, se puede usar así:

 var result = default(MyObject); bool isValidObject = jsonString.TryParseJson(out result); if(isValidObject) { // Do something } 

La respuesta de @Victor LG con Newtonsoft está cerca, pero técnicamente no evita la captura como solicitó el póster original. Simplemente lo mueve a otro lugar. Además, aunque crea una instancia de configuración para permitir la captura de miembros perdidos, esas configuraciones no se pasan a la llamada DeserializeObject, por lo que realmente se ignoran.

Aquí hay una versión “sin capturas” de su método de extensión que también incluye la bandera de miembros perdidos. La clave para evitar la captura es establecer la propiedad Error del objeto de configuración en una lambda que luego establece un indicador para indicar el error y borra el error para que no cause una excepción.

  public static bool TryParseJson(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(@this, settings); return success; } 

Aquí hay un ejemplo para usarlo:

 if(value.TryParseJson(out MyType result)) { // Do something with result… }