El mejor enfoque para analizar un archivo JSON enorme (extra grande)

7 minutos de lectura

Avatar de usuario de Dax
dax

Estoy tratando de analizar un gran archivo JSON (como http://eu.battle.net/auction-data/258993a3c6b974ef3e6f22ea6f822720/auctions.json) utilizando la biblioteca gson (http://code.google.com/p/google-gson/) en JAVA.

Me gustaría saber cuál es el mejor enfoque para analizar este tipo de archivo grande (alrededor de 80k líneas) y si conoce una buena API que pueda ayudarme a procesar esto.

Algunas ideas

  1. lea línea por línea y deshágase del formato JSON: pero eso es una tontería.
  2. reduzca el archivo JSON dividiendo este archivo en muchos otros: pero no encontré ninguna buena API de Java para esto.
  3. use este archivo directamente como base de datos no Sql, conserve el archivo y utilícelo como mi base de datos.

  • Una alternativa de Java EE: javax.json.stream.JsonParser

    – xonya

    19 de enero de 2018 a las 8:35

No es necesario cambiar a Jackson. Gson 2.1 introdujo un nuevo TipoAdaptador interfaz que permite la serialización y deserialización mixta de árbol y transmisión.

La API es eficiente y flexible. Ver Documento de transmisión de Gson para ver un ejemplo de combinación de árbol y vinculante modos. Esto es estrictamente mejor que los modos mixtos de transmisión y árbol; con el enlace, no desperdicia memoria construyendo una representación intermedia de sus valores.

Al igual que Jackson, Gson tiene API para omitir recursivamente un valor no deseado; Gson llama a esto saltarValor().

  • ¿Hay un buen ejemplo del uso de la TypeAdapter al análisis de flujo mixto en el análisis de árbol? Tengo un caso en el que quiero mezclarlo en una Lista de objetos que se vuelve muy grande. El ejemplo en la documentación es el flujo de análisis de una Lista de Messages pero no muestra cómo vincularía ese analizador de flujo en un analizador de árbol. (Muestra cómo vincular un analizador de árbol en un analizador de flujo)

    -Dandre Allison

    27 de febrero de 2013 a las 1:01

  • Por ejemplo: tengo CustomType para definir el mapeo de objetos, y CustomTypes extends ArrayList<CustomType>. Yo hago un TypeAdapter<CustomTypes> que utiliza el mapeo de objetos para cada CustomType, pero solo devuelve una lista vacía al final para evitar almacenar toda la lista en la memoria (en su lugar, escríbalos en una base de datos). Y luego el objeto contenedor se analiza simplemente usando el mapeo de objetos.

    -Dandre Allison

    27 de febrero de 2013 a las 1:41

  • @DandreAllison: Yo también necesitaba esto. La solución es crear un JsonParser y luego llamar parse(JsonReader json)que consumirá el siguiente valor y avanzará al lector.

    – Sebastián N.

    25 mayo 2016 a las 14:01

  • Me gusta esta idea sobre la transmisión json. ¡Bu arregla tu enlace roto TypeArray por favor!

    – Serg Burlaka

    16 de abril de 2018 a las 9:01

  • TypeAdapterFactory ?

    – aderchox

    13 de septiembre de 2021 a las 6:39

avatar de usuario de vikiiii
vikiiii

Sugeriré echar un vistazo a API de Jackson es muy fácil combinar las opciones de análisis de transmisión y modelo de árbol: puede moverse a través del archivo como un todo en una forma de transmisión y luego leer objetos individuales en una estructura de árbol.

como un ejemplotomemos la siguiente entrada:

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

Imagínese que los campos son escasos o los registros tienen una estructura más compleja.

El siguiente fragmento ilustra cómo se puede leer este archivo mediante una combinación de análisis de flujo y modelo de árbol. Cada registro individual se lee en una estructura de árbol, pero el archivo nunca se lee en su totalidad en la memoria, lo que hace posible procesar archivos JSON de gigabytes de tamaño utilizando una memoria mínima.

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

Como puede adivinar, la llamada nextToken() cada vez da el siguiente evento de análisis: objeto de inicio, campo de inicio, matriz de inicio, objeto de inicio, …, objeto final, …, matriz final, …

El jp.readValueAsTree() La llamada permite leer lo que está en la posición de análisis actual, un objeto o matriz JSON, en el modelo de árbol JSON genérico de Jackson. Una vez que tenga esto, puede acceder a los datos de forma aleatoria, independientemente del orden en que aparezcan las cosas en el archivo (en el ejemplo, el campo 1 y el campo 2 no siempre están en el mismo orden). Jackson también admite el mapeo en sus propios objetos Java. El jp.skipChildren() es conveniente: permite saltarse un árbol de objetos completo o una matriz sin tener que pasar por encima de todos los eventos que contiene.

  • ¡Tu código fue realmente útil! Lo ajusté a mi problema y finalmente pude deshacerme de mis excepciones de espacio de montón porque leí el archivo de una vez antes 🙂

    – Konrad Höffner

    21 de junio de 2013 a las 11:11

Mapeo de flujo declarativo (DSM) La biblioteca le permite definir asignaciones entre sus datos JSON o XML y su POJO. Por lo tanto, no necesita escribir un analizador personalizado. Tiene un potente soporte para secuencias de comandos (Javascript, Groovy, JEXL). Puede filtrar y transformar datos mientras lee. Puede llamar a funciones para la operación de datos parciales mientras lee datos. DSM lee los datos como una secuencia, por lo que utiliza muy poca memoria.

Por ejemplo,

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

imagine que el fragmento anterior es parte de datos JSON enormes y complejos. solo queremos conseguir cosas que tienen un salario superior a 10000.

En primer lugar, debemos definir las definiciones de mapeo de la siguiente manera. Como puede ver, es solo un archivo yaml que contiene la asignación entre los campos POJO y el campo de datos JSON.

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

Cree FunctionExecutor para el personal del proceso.

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

Utilice DSM para procesar 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);

Salida: (Solo se incluyen las cosas que tienen un salario superior a 10000)

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

¿Ha sido útil esta solución?