web-dev-qa-db-fra.com

Analyser un gros fichier JSON dans .NET

J'ai utilisé jusqu'à présent la méthode "JsonConvert.Deserialize (json)" de Json.NET qui fonctionnait assez bien et pour être honnête, je n'avais besoin de rien de plus.

Je travaille sur une application d'arrière-plan (console) qui télécharge constamment le contenu JSON de différentes URL, puis désérialise le résultat dans une liste d'objets .NET.

 using (WebClient client = new WebClient())
 {
      string json = client.DownloadString(stringUrl);

      var result = JsonConvert.DeserializeObject<List<Contact>>(json);

 }

L'extrait de code simple ci-dessus ne semble probablement pas parfait, mais il fait le travail. Lorsque le fichier est volumineux (15 000 contacts - fichier de 48 Mo), JsonConvert.DeserializeObject n'est pas la solution et la ligne lève un type d'exception de JsonReaderException.

Le contenu JSON téléchargé est un tableau et voici à quoi ressemble un échantillon. Contact est une classe de conteneur pour l'objet JSON désérialisé.

[
  {
    "firstname": "sometext",
    "lastname": "sometext"
  },
  {
    "firstname": "sometext",
    "lastname": "sometext"
  },
  {
    "firstname": "sometext",
    "lastname": "sometext"
  },
  {
    "firstname": "sometext",
    "lastname": "sometext"
  }
]

Ma première supposition est qu'il manque de mémoire. Juste par curiosité, j'ai essayé de l'analyser en tant que JArray, ce qui a également causé la même exception.

J'ai commencé à plonger dans la documentation Json.NET et à lire des discussions similaires. Comme je n'ai pas encore réussi à produire une solution de travail, j'ai décidé de poster une question ici.

MISE À JOUR: Lors de la désérialisation ligne par ligne, j'ai eu la même erreur: "[. Path '', ligne 600003, position 1." J'ai donc téléchargé deux d'entre eux et les ai vérifiés dans Notepad ++. J'ai remarqué que si la longueur du tableau est supérieure à 12 000, après le 12 000ème élément, le "[" est fermé et un autre tableau démarre. En d'autres termes, le JSON ressemble exactement à ceci:

[
  {
    "firstname": "sometext",
    "lastname": "sometext"
  },
  {
    "firstname": "sometext",
    "lastname": "sometext"
  },
  {
    "firstname": "sometext",
    "lastname": "sometext"
  },
  {
    "firstname": "sometext",
    "lastname": "sometext"
  }
]
[
  {
    "firstname": "sometext",
    "lastname": "sometext"
  },
  {
    "firstname": "sometext",
    "lastname": "sometext"
  },
  {
    "firstname": "sometext",
    "lastname": "sometext"
  },
  {
    "firstname": "sometext",
    "lastname": "sometext"
  }
]
29
Yavar Hasanov

Comme vous l'avez correctement diagnostiqué dans votre mise à jour, le problème est que le JSON a une fermeture ] suivi immédiatement d'une ouverture [ pour démarrer la série suivante. Ce format rend le JSON invalide lorsqu'il est pris dans son ensemble, et c'est pourquoi Json.NET génère une erreur.

Heureusement, ce problème semble se produire assez souvent pour que Json.NET ait en fait un paramètre spécial pour y faire face. Si vous utilisez un JsonTextReader directement pour lire le JSON, vous pouvez définir l'indicateur SupportMultipleContent sur true, puis utiliser une boucle pour désérialiser chaque élément individuellement.

Cela devrait vous permettre de traiter le JSON non standard avec succès et d'une manière efficace en mémoire, quel que soit le nombre de tableaux ou le nombre d'éléments dans chaque tableau.

    using (WebClient client = new WebClient())
    using (Stream stream = client.OpenRead(stringUrl))
    using (StreamReader streamReader = new StreamReader(stream))
    using (JsonTextReader reader = new JsonTextReader(streamReader))
    {
        reader.SupportMultipleContent = true;

        var serializer = new JsonSerializer();
        while (reader.Read())
        {
            if (reader.TokenType == JsonToken.StartObject)
            {
                Contact c = serializer.Deserialize<Contact>(reader);
                Console.WriteLine(c.FirstName + " " + c.LastName);
            }
        }
    }

Démo complète ici: https://dotnetfiddle.net/2TQa8p

37
Brian Rogers

Json.NET prend en charge la désérialisation directement à partir d'un flux. Voici un moyen de désérialiser votre JSON en utilisant un StreamReader en lisant la chaîne JSON une pièce à la fois au lieu d'avoir la chaîne JSON entière chargée en mémoire.

using (WebClient client = new WebClient())
{
    using (StreamReader sr = new StreamReader(client.OpenRead(stringUrl)))
    {
        using (JsonReader reader = new JsonTextReader(sr))
        {
            JsonSerializer serializer = new JsonSerializer();

            // read the json from a stream
            // json size doesn't matter because only a small piece is read at a time from the HTTP request
            IList<Contact> result = serializer.Deserialize<List<Contact>>(reader);
        }
    }
}

Référence: Conseils de performance JSON.NET

17
Kristian Vukusic

J'ai fait une chose similaire en Python pour la taille de fichier de 5 Go. J'ai téléchargé le fichier dans un emplacement temporaire et l'ai lu ligne par ligne pour former un objet JSON similaire sur le fonctionnement de SAX.

Pour C # à l'aide de Json.NET, vous pouvez télécharger le fichier, utiliser un lecteur de flux pour lire le fichier et transmettre ce flux à JsonTextReader et l'analyser vers JObject à l'aide de JTokens.ReadFrom(your JSonTextReader object).

5
nixdaemon