web-dev-qa-db-ja.com

.NETでの大規模なJSONファイルの解析

これまでのところ、Json.NETの「JsonConvert.Deserialize(json)」メソッドを使用しましたが、これは非常にうまく機能し、正直なところ、これ以上のものは必要ありませんでした。

私はさまざまなURLからJSONコンテンツを常にダウンロードし、その結果を.NETオブジェクトのリストに逆シリアル化するバックグラウンド(コンソール)アプリケーションで作業しています。

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

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

 }

上記の単純なコードスニペットはおそらく完璧とは思えませんが、仕事はします。ファイルが大きい場合(15,000件の連絡先-48 MBファイル)、JsonConvert.DeserializeObjectはソリューションではなく、行はJsonReaderExceptionの例外タイプをスローします。

ダウンロードされたJSONコンテンツは配列であり、これがサンプルの外観です。 Contactは、デシリアライズされたJSONオブジェクトのコンテナークラスです。

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

私の最初の推測では、メモリが不足しています。好奇心から、私はそれをJArrayとして解析しようとしましたが、これも同じ例外を引き起こしました。

私はJson.NETのドキュメントに飛び込み、同様のスレッドを読み始めました。まだ実用的なソリューションを作成できていないため、ここに質問を投稿することにしました。

更新:行ごとに逆シリアル化中に、同じエラーが発生しました: "[。Path ''、line 600003、position 1"そのため、そのうちの2つをダウンロードし、Notepad ++でチェックしました。配列の長さが12,000を超えると、12000番目の要素の後、「[」が閉じられ、別の配列が開始されることに気付きました。つまり、JSONは次のようになります。

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

更新で正しく診断されたように、問題はJSONに終了]の直後に開始[次のセットを開始します。この形式により、JSONは全体として無効になるため、Json.NETはエラーをスローします。

幸いなことに、この問題は、Json.NETが実際にそれを処理する特別な設定を持っているほど頻繁に発生するようです。 JsonTextReaderを直接使用してJSONを読み取る場合、SupportMultipleContentフラグをtrueに設定し、ループを使用して各アイテムを個別にデシリアライズできます。

これにより、配列の数や各配列内のアイテムの数に関係なく、非標準のJSONをメモリ効率の良い方法で正常に処理できるようになります。

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

完全なデモはこちら: https://dotnetfiddle.net/2TQa8p

37
Brian Rogers

Json.NETは、ストリームからの直接のデシリアライズをサポートしています。 JSON文字列全体をメモリにロードする代わりに、JSON文字列を一度に1つずつ読み取るStreamReaderを使用してJSONを逆シリアル化する方法を次に示します。

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

参照: JSON.NETパフォーマンスのヒント

17

Python 5 GBのファイルサイズに対して同様のことを行いました。一時的な場所にファイルをダウンロードし、1行ずつ読み取り、SAXの動作方法と同様のJSONオブジェクトを作成しました。

Json.NETを使用するC#の場合、ファイルをダウンロードし、ストリームリーダーを使用してファイルを読み取り、そのストリームをJsonTextReaderに渡し、JTokens.ReadFrom(your JSonTextReader object)を使用してJObjectに解析できます。

5
nixdaemon