c# json json.net

c# - ¿Cómo analizar un gran archivo JSON como flujo en Json.NET?



(3)

¿Es esto lo que estás buscando? Encontrado en una pregunta anterior

La versión actual de Json.net no le permite usar el código de respuesta aceptado. Una alternativa actual es:

public static object DeserializeFromStream(Stream stream) { var serializer = new JsonSerializer(); using (var sr = new StreamReader(stream)) using (var jsonTextReader = new JsonTextReader(sr)) { return serializer.Deserialize(jsonTextReader); } }

Documentación: Deserializar JSON de una secuencia de archivos

Tengo un archivo JSON muy, muy grande (más de 1000 MB) de objetos JSON idénticos. Por ejemplo:

[ { "id": 1, "value": "hello", "another_value": "world", "value_obj": { "name": "obj1" }, "value_list": [ 1, 2, 3 ] }, { "id": 2, "value": "foo", "another_value": "bar", "value_obj": { "name": "obj2" }, "value_list": [ 4, 5, 6 ] }, { "id": 3, "value": "a", "another_value": "b", "value_obj": { "name": "obj3" }, "value_list": [ 7, 8, 9 ] }, ... ]

Cada elemento individual en la lista JSON raíz sigue la misma estructura y, por lo tanto, sería deserializable individualmente. Ya tengo las clases de C # escritas para recibir estos datos, y deserializar un archivo JSON que contiene un solo objeto sin la lista funciona como se esperaba.

Al principio, traté de deserializar directamente mis objetos en un bucle:

JsonSerializer serializer = new JsonSerializer(); MyObject o; using (FileStream s = File.Open("bigfile.json", FileMode.Open)) using (StreamReader sr = new StreamReader(s)) using (JsonReader reader = new JsonTextReader(sr)) { while (!sr.EndOfStream) { o = serializer.Deserialize<MyObject>(reader); } }

Esto no funcionó, arrojó una excepción que indica claramente que se espera un objeto, no una lista. Entiendo que este comando solo leería un solo objeto contenido en el nivel raíz del archivo JSON, pero dado que tenemos una lista de objetos, esta es una solicitud no válida.

Mi siguiente idea fue deserializar como una lista de objetos C #:

JsonSerializer serializer = new JsonSerializer(); List<MyObject> o; using (FileStream s = File.Open("bigfile.json", FileMode.Open)) using (StreamReader sr = new StreamReader(s)) using (JsonReader reader = new JsonTextReader(sr)) { while (!sr.EndOfStream) { o = serializer.Deserialize<List<MyObject>>(reader); } }

Esto tiene éxito. Sin embargo, solo reduce un poco el problema del uso elevado de RAM. En este caso, parece que la aplicación está deserializando elementos de uno en uno, por lo que no está leyendo todo el archivo JSON en la RAM, pero todavía terminamos con un gran uso de RAM porque el objeto Lista C # ahora contiene todos los datos del archivo JSON en RAM. Esto solo ha desplazado el problema.

Entonces decidí simplemente intentar quitar un solo carácter del comienzo de la secuencia (para eliminar el [ ) haciendo sr.Read() antes de entrar en el bucle. El primer objeto se lee con éxito, pero los siguientes no, con la excepción de "token inesperado". Supongo que esta es la coma y el espacio entre los objetos que arrojan al lector.

Simplemente eliminar los corchetes no funcionará, ya que los objetos contienen una lista primitiva propia, como puede ver en la muestra. Incluso tratando de usar }, ya que un separador no funcionará ya que, como puede ver, hay sub-objetos dentro de los objetos.

Mi objetivo es poder leer los objetos de la transmisión de uno en uno. Lea un objeto, haga algo con él, luego deséchelo de la RAM y lea el siguiente objeto, y así sucesivamente. Esto eliminaría la necesidad de cargar la cadena JSON completa o todo el contenido de los datos en la RAM como objetos C #.

¿Qué me estoy perdiendo?


Creo que podemos hacerlo mejor que la respuesta aceptada, utilizando más funciones de JsonReader para hacer una solución más generalizada.

Como un JsonReader consume tokens de un JSON, la ruta se registra en la propiedad JsonReader.Path .

Podemos usar esto para seleccionar con precisión datos profundamente anidados de un archivo JSON, utilizando expresiones regulares para asegurarnos de que estamos en el camino correcto.

Entonces, usando el siguiente método de extensión:

public static class JsonReaderExtensions { public static IEnumerable<T> SelectTokensWithRegex<T>( this JsonReader jsonReader, Regex regex) { JsonSerializer serializer = new JsonSerializer(); while (jsonReader.Read()) { if (regex.IsMatch(jsonReader.Path) && jsonReader.TokenType != JsonToken.PropertyName) { yield return serializer.Deserialize<T>(jsonReader); } } } }

Los datos que le interesan se encuentran en las rutas:

[0] [1] [2] ... etc

Podemos construir la siguiente expresión regular para que coincida con precisión con esta ruta:

var regex = new Regex(@"^/[/d+/]$");

ahora es posible transmitir objetos fuera de sus datos (sin cargar completamente o analizar todo el JSON) de la siguiente manera

IEnumerable<MyObject> objects = jsonReader.SelectTokensWithRegex<MyObject>(regex);

O si queremos profundizar aún más en la estructura, podemos ser aún más precisos con nuestra expresión regular

var regex = new Regex(@"^/[/d+/]/.value$"); IEnumerable<string> objects = jsonReader.SelectTokensWithRegex<string>(regex);

para extraer solo propiedades de value de los elementos de la matriz.

He encontrado que esta técnica es extremadamente útil para extraer datos específicos de grandes volcados JSON (100 GiB), directamente desde HTTP utilizando una secuencia de red (con bajos requisitos de memoria y sin necesidad de almacenamiento intermedio).


Esto debería resolver tu problema. Básicamente funciona igual que su código inicial, excepto que solo deserializa el objeto cuando el lector golpea el carácter { en la secuencia y, de lo contrario, simplemente salta al siguiente hasta encontrar otro token de objeto de inicio.

JsonSerializer serializer = new JsonSerializer(); MyObject o; using (FileStream s = File.Open("bigfile.json", FileMode.Open)) using (StreamReader sr = new StreamReader(s)) using (JsonReader reader = new JsonTextReader(sr)) { while (reader.Read()) { // deserialize only when there''s "{" character in the stream if (reader.TokenType == JsonToken.StartObject) { o = serializer.Deserialize<MyObject>(reader); } } }