Json.Net deserializa el problema de falta de memoria

Json.Net deserializa el problema de falta de memoria

Para leer una cadena JSON grande con el uso de JsonConvert.DeserializeObject consumirá mucha memoria. Entonces, una de las formas de superar este problema es crear una instancia de JsonSerializer como se indica a continuación.

 using (StreamReader r = new StreamReader(filePath))
 {
          using (JsonReader reader = new JsonTextReader(r))
         {
                JsonSerializer serializer = new JsonSerializer();
                T lstObjects = serializer.Deserialize<T>(reader);
        }
}

Aquí ruta del archivo :- es su archivo Json actual y T :- es su objeto de tipo Genérico.


Tienes dos problemas aquí:

  1. Tiene un campo de datos Base64 único dentro de su respuesta JSON que es más grande que ~400 MB.

  2. Está cargando la respuesta completa en una cadena intermedia jsonContent que es aún más grande ya que incrusta el campo de datos único.

En primer lugar, supongo que está utilizando 64 bits. Si no, cambia.

Desafortunadamente, el primer problema solo se puede mejorar y no solucionar porque JsonTextReader de Json.NET no tiene la capacidad de leer un solo valor de cadena en "trozos" de la misma manera que XmlReader.ReadValueChunk() . Siempre materializará completamente cada valor de cadena atómica. Pero .Net 4.5 agrega las siguientes configuraciones que pueden ayudar:

  1. <gcAllowVeryLargeObjects enabled="true" /> .

  2. GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce .

    Esta configuración permite compactar el montón de objetos grandes y puede reducir los errores de falta de memoria debido a la fragmentación del espacio de direcciones.

Sin embargo, el segundo problema se puede abordar mediante la deserialización de la transmisión, como se muestra en esta respuesta a esta pregunta de Dilip0165; Llamadas API eficientes con HttpClient y JSON.NET por John Thiriet; Sugerencias de rendimiento:optimizar el uso de la memoria por Newtonsoft; y Streaming con New .NET HttpClient y HttpCompletionOption.ResponseHeadersRead por Tugberk Ugurlu. Reuniendo la información de estas fuentes, su código debería verse como:

Result result;
var requestJson = JsonConvert.SerializeObject(message); // Here we assume the request JSON is not too large
using (var requestContent = new StringContent(requestJson, Encoding.UTF8, "application/json"))
using (var request = new HttpRequestMessage(HttpMethod.Post, client.BaseAddress) { Content = requestContent })
using (var response = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).Result)
using (var responseStream = response.Content.ReadAsStreamAsync().Result)
{
    if (response.IsSuccessStatusCode)
    {
        using (var textReader = new StreamReader(responseStream))
        using (var jsonReader = new JsonTextReader(textReader))
        {
            result = JsonSerializer.CreateDefault().Deserialize<Result>(jsonReader);
        }
    }
    else
    {
        // TODO: handle an unsuccessful response somehow, e.g. by throwing an exception
    }
}

O, usando async/await :

Result result;
var requestJson = JsonConvert.SerializeObject(message); // Here we assume the request JSON is not too large
using (var requestContent = new StringContent(requestJson, Encoding.UTF8, "application/json"))
using (var request = new HttpRequestMessage(HttpMethod.Post, client.BaseAddress) { Content = requestContent })
using (var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead))
using (var responseStream = await response.Content.ReadAsStreamAsync())
{
    if (response.IsSuccessStatusCode)
    {
        using (var textReader = new StreamReader(responseStream))
        using (var jsonReader = new JsonTextReader(textReader))
        {
            result = JsonSerializer.CreateDefault().Deserialize<Result>(jsonReader);
        }
    }
    else
    {
        // TODO: handle an unsuccessful response somehow, e.g. by throwing an exception
    }
}           

Mi código anterior no está completamente probado, y es necesario implementar el manejo de errores y cancelaciones. Es posible que también deba configurar el tiempo de espera como se muestra aquí y aquí. JsonSerializer de Json.NET no es compatible con la deserialización asíncrona, por lo que encaja un poco con el modelo de programación asíncrona de HttpClient .

Por último, como alternativa al uso de Json.NET para leer un gran fragmento de Base64 de un archivo JSON, puede usar el lector devuelto por JsonReaderWriterFactory que hace Admite la lectura de datos Base64 en fragmentos manejables. Para obtener más información, consulte esta respuesta a Analice enormes JSON de OData transmitiendo ciertas secciones del json para evitar LOH para obtener una explicación de cómo transmitir a través de un archivo JSON enorme con este lector, y esta respuesta a Leer flujo desde XmlReader, decodificarlo en base64 y escribir el resultado en el archivo para saber cómo decodificar datos Base64 en fragmentos usando XmlReader.ReadElementContentAsBase64


Las enormes cadenas base64 no son un problema como tal, .Net admite tamaños de objeto de alrededor de 2 gb, vea la respuesta aquí. ¡Por supuesto, eso no significa que pueda almacenar 2 gb de información en un objeto!

Sin embargo, tengo la sensación de que el byte[] es el problema.

Si hay demasiados elementos para que los contenga un byte[], no importa si transmite el resultado o incluso si lo lee desde un archivo en su disco duro.

Entonces, solo con fines de prueba, ¿puede intentar cambiar el tipo de byte[] a cadena o incluso a una lista? No es elegante ni aconsejable, pero podría señalar el camino hacia una mejor solución.

Editar:

Otro caso de prueba para probar, en lugar de llamar a deserializeObject, simplemente intente guardar esa cadena jsonContent en un archivo y vea qué tan grande es.

Además, ¿por qué lo necesitas en la memoria? ¿Qué tipo de datos son? Me parece que si tiene que procesar esto en la memoria, lo pasará mal:el tamaño del objeto es simplemente demasiado grande para el CLR.

Sin embargo, solo tuve un poco de inspiración, ¿qué tal probar un deserializador diferente? Quizás RestSharp o puede usar HttpClient.ReadAsAsync<T> . Es posible que sea el mismo NewtonSoft el que tenga un problema, especialmente si el tamaño del contenido ronda los 400 mb.