web-dev-qa-db-ja.com

JAVA-巨大な(特大の)JSONファイルを解析する最適なアプローチ

私はいくつかの巨大なJSONファイルを解析しようとしています( http://eu.battle.net/auction-data/258993a3c6b974ef3e6f22ea6f822720/auctions.json ) Javaでgsonライブラリ( http://code.google.com/p/google-gson/ )を使用します。

この種の大きなファイル(約80k行)を解析するための最良の方法は何か、そしてこれを処理するのに役立つ優れたAPIを知っているかもしれません。

いくつかのアイデア...

  1. 行ごとに読み取り、JSON形式を取り除きます。しかし、それはナンセンスです。
  2. このファイルを他の多くのファイルに分割してJSONファイルを削減します。ただし、これに適したAPI Java。
  3. このファイルをnonSqlデータベースとして直接使用し、ファイルを保持してデータベースとして使用します。

私は本当に感謝/ヘルプ/メッセージ/ :-)ありがとうございます。

40
Dax

ジャクソンに切り替える必要はありません。 Gson 2.1は、新しい TypeAdapter インターフェイスを導入しました。これにより、ツリーとストリーミングのシリアル化と逆シリアル化が混在できます。

APIは効率的で柔軟です。ツリーとbindingモードの組み合わせの例については、 Gson's Streaming doc を参照してください。これは、ストリーミングモードとツリーモードが混在する場合よりも厳密に優れています。バインディングを使用すると、値の中間表現を構築するためにメモリを浪費することはありません。

ジャクソンと同様に、Gsonには不要な値を再帰的にスキップするAPIがあります。 Gsonはこれを skipValue() と呼びます。

35
Jesse Wilson

Jackson Api をご覧になることをお勧めします。ストリーミングオプションとツリーモデル解析オプションを組み合わせるのは非常に簡単です。オブジェクトをツリー構造に。

として、次の入力を見てみましょう。

_{ 
  "records": [ 
    {"field1": "aaaaa", "bbbb": "ccccc"}, 
    {"field2": "aaa", "bbb": "ccc"} 
  ] ,
  "special message": "hello, world!" 
}
_

フィールドがまばらであることや、レコードがより複雑な構造を持っていることを想像してください。

次のスニペットは、ストリームとツリーモデル解析の組み合わせを使用してこのファイルを読み取る方法を示しています。個々のレコードはツリー構造で読み込まれますが、ファイル全体がメモリに読み込まれることはないため、最小限のメモリを使用しながら、サイズがギガバイトのJSONファイルを処理できます。

_import org.codehaus.jackson.map.*;
import org.codehaus.jackson.*;

import Java.io.File;

public class ParseJsonSample {
    public static void main(String[] args) throws Exception {
        JsonFactory f = new MappingJsonFactory();
        JsonParser jp = f.createJsonParser(new File(args[0]));
        JsonToken current;
        current = jp.nextToken();
        if (current != JsonToken.START_OBJECT) {
            System.out.println("Error: root should be object: quiting.");
            return;
        }
        while (jp.nextToken() != JsonToken.END_OBJECT) {
            String fieldName = jp.getCurrentName();
            // move from field name to field value
            current = jp.nextToken();
            if (fieldName.equals("records")) {
                if (current == JsonToken.START_ARRAY) {
                    // For each of the records in the array
                    while (jp.nextToken() != JsonToken.END_ARRAY) {
                        // read the record into a tree model,
                        // this moves the parsing position to the end of it
                        JsonNode node = jp.readValueAsTree();
                        // And now we have random access to everything in the object
                        System.out.println("field1: " + node.get("field1").getValueAsText());
                        System.out.println("field2: " + node.get("field2").getValueAsText());
                    }
                } else {
                    System.out.println("Error: records should be an array: skipping.");
                    jp.skipChildren();
                }
            } else {
                System.out.println("Unprocessed property: " + fieldName);
                jp.skipChildren();
            }
        }
    }
}
_

ご想像のとおり、nextToken()呼び出しは毎回次の解析イベントを提供します:開始オブジェクト、開始フィールド、開始配列、開始オブジェクト、...、終了オブジェクト、...、終了配列、...

jp.readValueAsTree()呼び出しにより、現在の解析位置にあるもの、JSONオブジェクトまたは配列をJacksonの汎用JSONツリーモデルに読み込むことができます。これを取得したら、ファイルに表示される順序に関係なく、データにランダムにアクセスできます(例では、field1とfield2は常に同じ順序ではありません)。ジャクソンは、独自のJavaオブジェクトへのマッピングもサポートしています。その中に。

36
vikiiii

宣言型ストリームマッピング(DSM) ライブラリを使用すると、JSONまたはXMLデータとPOJOの間のマッピングを定義できます。そのため、カスタムパーサーを記述する必要はありません。強力なスクリプト(Javascript、groovy、JEXL)をサポートしています。読み取り中にデータをフィルタリングおよび変換できます。データの読み取り中に、部分的なデータ操作の関数を呼び出すことができます。 DSMはデータをストリームとして読み取るため、非常に少ないメモリを使用します。

例えば、

{
    "company": {
         ....
        "staff": [
            {
                "firstname": "yong",
                "lastname": "mook kim",
                "nickname": "mkyong",
                "salary": "100000"
            },
            {
                "firstname": "low",
                "lastname": "yin fong",
                "nickname": "fong fong",
                "salary": "200000"
            }
        ]
    }
}

上記のスニペットが巨大で複雑なJSONデータの一部であることを想像してください。取得したいのは10000を超える給与を持つものです。

まず、マッピング定義を次のように定義する必要があります。ご覧のとおり、これはPOJOフィールドとJSONデータのフィールド間のマッピングを含む単なるyamlファイルです。

result:
      type: object     # result is map or a object.
      path: /.+staff  # path is regex. its match with /company/staff
      function: processStuff  # call processStuff function when /company/stuff tag is closed
      filter: self.data.salary>10000   # any expression is valid in JavaScript, Groovy or JEXL
      fields:
        name:  
          path: firstname
        sureName:
          path: lastname
        userName:
          path: nickname
        salary: long

プロセススタッフのFunctionExecutorを作成します。

FunctionExecutor processStuff=new FunctionExecutor(){

            @Override
            public void execute(Params params) {

                // directly serialize Stuff class
                //Stuff stuff=params.getCurrentNode().toObject(Stuff.class);

                Map<String,Object> stuff= (Map<String,Object>)params.getCurrentNode().toObject();
                System.out.println(stuff);
                // process stuff ; save to db. call service etc.
            }
        };

DSMを使用してJSONを処理する

     DSMBuilder builder = new DSMBuilder(new File("path/to/mapping.yaml")).setType(DSMBuilder.TYPE.XML);

       // register processStuff Function
        builder.registerFunction("processStuff",processStuff);

        DSM dsm= builder.create();
        Object object =  dsm.toObject(xmlContent);

出力:(給与が10000を超えるもののみが含まれます)

{firstName=low, lastName=yin fong, nickName=fong fong, salary=200000}
1
mfe