Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 19 de abril de 2022
.NET

La semana pasada veíamos algunas alternativas para comprobar de forma eficiente si una cadena de texto contiene un JSON válido y, al hilo de dicho post, el amigo Javier Campos aportó vía Twitter una fórmula adicional mejor que las que vimos aquí.

El código en cuestión es el siguiente:

public bool IsJsonWithReader(string maybeJson)
{
    try
    {
        var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(maybeJson));
        reader.Read();
        reader.Skip();
        return true;
    }
    catch
    {
        return false;
    }
}

La estructura Utf8JsonReader ofrece una API de alto rendimiento para acceder en modo forward-only y read-only al JSON presente en una secuencia de bytes UTF8. Los métodos Read() y Skip() se encargan respectivamente de leer el primer token del JSON y saltarse todos sus hijos, con lo que en la práctica se recorrerá el documento completo, lanzándose una excepción en caso de ser inválido.

Veamos qué ocurre si sometemos este método al imparcial juicio de BenchmarkDotNet, ejecutando las pruebas sobre un JSON válido. Como podemos apreciar en la tabla inferior, se trata de la opción más rápida, y además la que menos memoria consume:

|                           Method |     Mean |    Error |   StdDev |  Gen 0 |  Gen 1 | Allocated |
|--------------------------------- |---------:|---------:|---------:|-------:|-------:|----------:|
|                JsonDocumentParse | 36.44 us | 0.276 us | 0.258 us | 3.9063 | 0.4272 |     32 KB |
| JsonSerializerDeserializeGeneric | 49.72 us | 0.288 us | 0.270 us | 1.7090 | 0.0610 |     14 KB |
|  JsonSerializerDeserializeObject | 63.34 us | 0.721 us | 0.674 us | 2.6855 |      - |     22 KB |
|         IsJsonWithUtf8JsonReader | 26.16 us | 0.259 us | 0.242 us | 1.4343 |      - |     12 KB |

De hecho, la memoria usada del heap incluso podríamos dejarla a cero si la entrada al método no fuera una cadena de caracteres, sino un array de bytes. O en otras palabras, la memoria que vemos ocupada se debe al uso de Encoding.Utf8.GetBytes() para convertir la cadena a la secuencia de bytes que la clase Utf8JsonReader necesita, y no al parseo del documento JSON contenido en ella.

Veamos ahora qué ocurre cuando el archivo no es un JSON correcto. En este caso, también la alternativa del Utf8JsonReader es la más rápida, aunque ocupa bastante más memoria que el resto debido principalmente la llamada a Encoding.Utf8.GetBytes() que comentábamos más arriba:

|                           Method |      Mean |     Error |    StdDev |  Gen 0 |  Gen 1 | Allocated |
|--------------------------------- |----------:|----------:|----------:|-------:|-------:|----------:|
|                JsonDocumentParse | 25.498 us | 0.0864 us | 0.0675 us | 0.1526 |      - |   1,400 B |
| JsonSerializerDeserializeGeneric | 26.916 us | 0.2180 us | 0.1933 us | 0.4272 |      - |   3,680 B |
|  JsonSerializerDeserializeObject | 29.339 us | 0.0833 us | 0.0779 us | 0.3357 |      - |   2,920 B |
|         IsJsonWithUtf8JsonReader |  9.541 us | 0.0477 us | 0.0446 us | 1.5717 | 0.0305 |  13,192 B |

Por tanto, si buscamos exclusivamente rendimiento, Utf8JsonReader es sin duda la mejor opción. Y si lo que más nos preocupa es la memoria, también es una buena alternativa si la mayoría de los textos se espera que sean JSON válidos.

¡Muchas gracias Javier por la aportación!

Publicado en Variable not found.

Aún no hay comentarios, ¡sé el primero!