Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

Verificar si una cadena de texto es un JSON válido

Al hilo del post Cómo recibir un Json como string en una acción ASP.NET Core MVC, el amigo Alberto dejaba una interesante pregunta en los comentarios: ¿y si una vez hemos recibido el string, queremos validar que sea un JSON válido?

Obviamente, una forma sencilla sería intentar deserializarlo por completo a la clase de destino, siempre que ésta sea conocida. Para ello podríamos utilizar el método Deserialize() del objeto JsonSerializer de System.Text.Json, disponible en todas las versiones modernas de .NET, de la siguiente manera:

public bool IsJson(string maybeJson)
{
try {
var obj = JsonSerializer.Deserialize(maybeJson);
return true;
} catch
{
return false;
}
}

Si no tenemos o conocemos la clase específica a la que habría que deserializar, podríamos hacerlo de forma genérica sobre un object:

public bool IsJson(string maybeJson)
{
try {
var obj = JsonSerializer.Deserialize(maybeJson);
return true;
} catch
{
return false;
}
}

Pero claro, si en realidad no nos interesa hacer nada con los valores del JSON, sino simplemente queremos saber si es JSON válido o no, con estas opciones anteriores estaríamos matando moscas a cañonazos. Quizás sería una mejor opción utilizar JsonDocument.Parse(), que creará la estructura en memoria con el contenido del JSON, pero no intentará materializarlo sobre ningún objeto del CLR:

public bool IsJsonDocument(string maybeJson)
{
try
{
var obj = JsonDocument.Parse(maybeJson);
return true;
}
catch
{
return false;
}
}

Mmmm... y entonces, ¿cuál es la mejor forma?

Cualquiera de las opciones anteriores son eficientes y muy rápidas, por lo que la mayoría de las veces podremos elegir la que mejor nos venga. Pero si queremos afinar algo más, como ocurre casi siempre, la mejor opción depende de lo que vayamos buscando ;)

Para comprobarlo, vamos a recurrir a nuestro viejo amigo BenchmarkDotNet. Creamos una aplicación de consola, con una referencia al paquete NuGet BenchMarkDotNet.

Añadimos en la carpeta del proyecto un archivo JSON de prueba (sample.json) y creamos también en él un objeto .NET con su estructura (esto podemos generarlo fácilmente mediante la opción de Visual Studio Edit > Paste Special> Paste JSON as classes).

Creamos también una clase con las pruebas a realizar:

[MemoryDiagnoser]
public class JsonCheckBenchmarks
{
public string _maybeJson;

[GlobalSetup]
public void Setup()
{
var filePath = Path.Combine(Assembly.GetCallingAssembly().Location, "..", "sample.json");
_maybeJson = File.ReadAllText(filePath);
}

[Benchmark]
public void JsonDocumentParse()
{
IsJsonDocument(_maybeJson);
}

[Benchmark]
public void JsonSerializerDeserializeGeneric()
{
IsJson(_maybeJson);
}

[Benchmark]
public void JsonSerializerDeserializeObject()
{
IsJson(_maybeJson);
}

... // Los métodos IsJson(), IsJson() y IsJsonDocument() que hemos visto antes
}

Y finalmente, lanzamos las pruebas desde el archivo Program.cs:

BenchmarkRunner.Run(); // Go!

Al ejecutar el proyecto (¡recordad, siempre en modo Release!) se ejecutarán las pruebas para comparar las tres fórmulas utilizadas para chequear la validez del JSON. Tras esperar unos minutos, el resultado que obtenemos es el siguiente cuando el contenido del archivo JSON es correcto:

|                           Method |     Mean |     Error |    StdDev |  Gen 0 | Allocated |
|--------------------------------- |---------:|----------:|----------:|-------:|----------:|
| JsonDocumentParse | 1.923 us | 0.0109 us | 0.0096 us | 0.2594 | 2 KB |
| JsonSerializerDeserializeGeneric | 2.686 us | 0.0071 us | 0.0059 us | 0.1411 | 1 KB |
| JsonSerializerDeserializeObject | 3.279 us | 0.0115 us | 0.0096 us | 0.1602 | 1 KB |

Como podemos observar, cuando el JSON es correcto, la opción más rápida es usar JsonDocument.Parse(). Tiene sentido, puesto que nos ahorramos todo el trabajo de crear un nuevo objeto y cargar sus propiedades vía reflexión. Y de las opciones que usan deserialización, la que utiliza el tipo concreto es bastante más rápida que la que deserializa a object.

Sin embargo, se puede observar también que las opciones que deserializan el objeto desde JSON, aunque menos rápidas, son más eficientes en términos de memoria. También tiene sentido, pues no tienen crear la estructura en memoria para alojar el JSON, sino crear los objetos destino de la deserialización y sus propiedades.

Por tanto, si pensamos que la mayoría de los JSON que nos lleguen serán correctos, deberíamos elegir JsonDocument.Parse() si queremos optimizar en velocidad, o la deserialización genérica JsonSerializer.Deserialize() si nos interesa optimizar la memoria.

En cambio, al introducir algún problema en el archivo JSON (por ejemplo, eliminar la llave de cierre), los datos cambian un poco:

|                           Method |     Mean |    Error |   StdDev |  Gen 0 | Allocated |
|--------------------------------- |---------:|---------:|---------:|-------:|----------:|
| JsonDocumentParse | 24.86 us | 0.043 us | 0.036 us | 0.1831 | 2 KB |
| JsonSerializerDeserializeGeneric | 26.20 us | 0.509 us | 0.476 us | 0.5798 | 5 KB |
| JsonSerializerDeserializeObject | 29.70 us | 0.116 us | 0.096 us | 0.4272 | 4 KB |

JsonDocument.Parse() sigue siendo la opción más rápida, aunque con muy poca diferencia sobre las opciones de deserialización completa, pero también es el que menos memoria consume. Por tanto, si la mayoría de JSON serán incorrectos, la primera opción será la más razonable.

Publicado en Variable not found.



This post first appeared on Variable Not Found, please read the originial post: here

Share the post

Verificar si una cadena de texto es un JSON válido

×

Subscribe to Variable Not Found

Get updates delivered right to your inbox!

Thank you for your subscription

×