web-dev-qa-db-fra.com

Comment analyser un énorme fichier JSON en tant que flux dans Json.NET?

J'ai un très très gros fichier JSON (plus de 1000 Mo) d'objets JSON identiques. Par exemple:

[
    {
        "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
        ]

    },
    ...
]

Chaque élément de la liste JSON racine suit la même structure et peut donc être désérialisable individuellement. J'ai déjà les classes C # écrites pour recevoir ces données et la désérialisation d'un fichier JSON contenant un seul objet sans la liste fonctionne comme prévu.

Au début, j'ai essayé de désérialiser directement mes objets dans une boucle:

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

Cela n'a pas fonctionné, a lancé une exception indiquant clairement qu'un objet est attendu, pas une liste. Si j'ai bien compris, cette commande lirait simplement un seul objet contenu au niveau racine du fichier JSON, mais comme nous avons un objet list of, il s’agit d’une requête non valide.

Ma prochaine idée était de désérialiser sous forme de liste C # d'objets:

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

Cela réussit. Cependant, cela ne réduit que légèrement le problème de l'utilisation RAM élevée. Dans ce cas, il semblerait que l'application désérialise les éléments un à un. Par conséquent, la totalité du fichier JSON n'est pas lue dans la RAM, mais nous avons toujours beaucoup d'utilisation de RAM, car l'objet C # List maintenant. contient toutes les données du fichier JSON dans la RAM. Cela n'a fait que déplacer le problème.

J'ai alors décidé d'essayer simplement de supprimer un seul caractère du début du flux (pour éliminer le [) en effectuant sr.Read() avant d'entrer dans la boucle. Le premier objet est alors lu avec succès, mais pas les suivants, à l'exception de "jeton inattendu". À mon avis, c’est la virgule et l’espace entre les objets qui jettent le lecteur.

Supprimer simplement les crochets ne fonctionnera pas, car les objets contiennent une liste primitive, comme vous pouvez le voir dans l'exemple. Même essayer d'utiliser }, en tant que séparateur ne fonctionnera pas car, comme vous pouvez le constater, il y a des sous-objets dans les objets.

Mon objectif est de pouvoir lire les objets du flux un par un. Lisez un objet, faites-en quelque chose, puis supprimez-le de la RAM, lisez le prochain objet, etc. Cela éviterait de charger la totalité de la chaîne JSON ou tout le contenu des données dans RAM en tant qu'objets C #. 

Qu'est-ce que je rate?

10
fdmillion

Cela devrait résoudre votre problème. Fondamentalement, cela fonctionne exactement comme votre code initial, sauf que la désérialisation d'objet n'est effectuée que lorsque le lecteur tape le caractère { dans le flux. Sinon, vous passez au suivant jusqu'à trouver un autre jeton d'objet initial.

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);
        }
    }
}
16
nocodename

vous pouvez utiliser un package de nuget simple comportant les méthodes d'extension simples décrites ci-dessus JStreamAsyncNet

0