La forma más rápida de aplanar / no aplanar objetos JSON anidados

159

Lancé un código para aplanar y desaplanar objetos JSON complejos / anidados. Funciona, pero es un poco lento (activa la advertencia de 'script largo').

Para los nombres planos quiero "." como delimitador e [INDICE] para matrices.

Ejemplos:

un-flattened | flattened
---------------------------
{foo:{bar:false}} => {"foo.bar":false}
{a:[{b:["c","d"]}]} => {"a[0].b[0]":"c","a[0].b[1]":"d"}
[1,[2,[3,4],5],6] => {"[0]":1,"[1].[0]":2,"[1].[1].[0]":3,"[1].[1].[1]":4,"[1].[2]":5,"[2]":6}

Creé un punto de referencia que ~ simula mi caso de uso http://jsfiddle.net/WSzec/

  • Obtener un objeto JSON anidado
  • Aplanarlo
  • Mire a través de él y posiblemente lo modifique mientras está aplanado
  • Descomprimirlo de nuevo a su formato original anidado para ser enviado

Me gustaría un código más rápido: para aclarar, el código que completa el punto de referencia JSFiddle ( http://jsfiddle.net/WSzec/ ) significativamente más rápido (~ 20% + sería bueno) en IE 9+, FF 24+ y Chrome 29 +.

Aquí está el código JavaScript relevante: Actual más rápido: http://jsfiddle.net/WSzec/6/

JSON.unflatten = function(data) {
    "use strict";
    if (Object(data) !== data || Array.isArray(data))
        return data;
    var result = {}, cur, prop, idx, last, temp;
    for(var p in data) {
        cur = result, prop = "", last = 0;
        do {
            idx = p.indexOf(".", last);
            temp = p.substring(last, idx !== -1 ? idx : undefined);
            cur = cur[prop] || (cur[prop] = (!isNaN(parseInt(temp)) ? [] : {}));
            prop = temp;
            last = idx + 1;
        } while(idx >= 0);
        cur[prop] = data[p];
    }
    return result[""];
}
JSON.flatten = function(data) {
    var result = {};
    function recurse (cur, prop) {
        if (Object(cur) !== cur) {
            result[prop] = cur;
        } else if (Array.isArray(cur)) {
             for(var i=0, l=cur.length; i<l; i++)
                 recurse(cur[i], prop ? prop+"."+i : ""+i);
            if (l == 0)
                result[prop] = [];
        } else {
            var isEmpty = true;
            for (var p in cur) {
                isEmpty = false;
                recurse(cur[p], prop ? prop+"."+p : p);
            }
            if (isEmpty)
                result[prop] = {};
        }
    }
    recurse(data, "");
    return result;
}

EDITAR 1 Modificó lo anterior a la implementación de @Bergi que actualmente es la más rápida. Además, el uso de ".indexOf" en lugar de "regex.exec" es aproximadamente un 20% más rápido en FF pero un 20% más lento en Chrome; así que me quedaré con la expresión regular ya que es más simple (aquí está mi intento de usar indexOf para reemplazar la expresión regular http://jsfiddle.net/WSzec/2/ ).

EDITAR 2 Basándome en la idea de @Bergi, logré crear una versión más rápida sin expresiones regulares (3 veces más rápido en FF y ~ 10% más rápido en Chrome). http://jsfiddle.net/WSzec/6/ En esta (la actual) implementación, las reglas para los nombres de clave son simplemente, las claves no pueden comenzar con un número entero o contener un punto.

Ejemplo:

  • {"foo": {"bar": [0]}} => {"foo.bar.0": 0}

EDITAR 3 Agregar el enfoque de análisis de ruta en línea de @AaditMShah (en lugar de String.split) ayudó a mejorar el rendimiento no aplanado. Estoy muy contento con la mejora del rendimiento general alcanzada.

Los últimos jsfiddle y jsperf:

http://jsfiddle.net/WSzec/14/

http://jsperf.com/flatten-un-flatten/4

Louis Ricci
fuente
77
No existe tal cosa como un "objeto JSON" . La pregunta parece ser sobre los objetos JS.
Felix Kling
1
Esta pregunta parece ser más apropiada para el sitio de revisión de código StackExchange: codereview.stackexchange.com
Aadit M Shah
66
@FelixKling: por objeto JSON me refería a objetos JS que solo contienen tipos primitivos de JavaScript. Podría, por ejemplo, poner una función en un objeto JS, pero no se serializaría en JSON, es decir, JSON.stringify ({fn: function () {alert ('a');}}); -
Louis Ricci
2
[1].[1].[0]me parece mal ¿Estás seguro de que este es el resultado deseado?
Bergi
2
Desafortunadamente, hay un error: los objetos de fecha se convierten en un JSON vacío.
giacecco

Respuestas:

217

Aquí está mi implementación mucho más corta:

Object.unflatten = function(data) {
    "use strict";
    if (Object(data) !== data || Array.isArray(data))
        return data;
    var regex = /\.?([^.\[\]]+)|\[(\d+)\]/g,
        resultholder = {};
    for (var p in data) {
        var cur = resultholder,
            prop = "",
            m;
        while (m = regex.exec(p)) {
            cur = cur[prop] || (cur[prop] = (m[2] ? [] : {}));
            prop = m[2] || m[1];
        }
        cur[prop] = data[p];
    }
    return resultholder[""] || resultholder;
};

flattenno ha cambiado mucho (y no estoy seguro de si realmente necesita esos isEmptycasos):

Object.flatten = function(data) {
    var result = {};
    function recurse (cur, prop) {
        if (Object(cur) !== cur) {
            result[prop] = cur;
        } else if (Array.isArray(cur)) {
             for(var i=0, l=cur.length; i<l; i++)
                 recurse(cur[i], prop + "[" + i + "]");
            if (l == 0)
                result[prop] = [];
        } else {
            var isEmpty = true;
            for (var p in cur) {
                isEmpty = false;
                recurse(cur[p], prop ? prop+"."+p : p);
            }
            if (isEmpty && prop)
                result[prop] = {};
        }
    }
    recurse(data, "");
    return result;
}

Juntos, ejecutan su punto de referencia en aproximadamente la mitad del tiempo (Opera 12.16: ~ 900ms en lugar de ~ 1900ms, Chrome 29: ~ 800ms en lugar de ~ 1600ms).

Nota: Esta y la mayoría de las otras soluciones respondidas aquí se centran en la velocidad y son susceptibles a la contaminación del prototipo y no deben usarse en objetos no confiables.

Bergi
fuente
1
¡Esto es genial! La expresión regular se ejecuta notablemente bien (especialmente en Chrome), intenté reemplazarla con la lógica indexOf, pero solo pude lograr una aceleración en FF. Agregaré una recompensa a esta pregunta para ver si se puede generar otra mejora inteligente, pero hasta ahora esto es más de lo que esperaba.
Louis Ricci
1
Logré sacar más velocidad de su implementación reemplazando regex.exec () con string.split () y simplificando el formato de la clave. Lo daré unos días antes de otorgarle los puntos, pero creo que se ha alcanzado el 'muro de optimización significativa'.
Louis Ricci
JSON.flatten ({}); // {'': {}} - podrías agregar una línea después de var result = {}; - si (resultado === datos) devuelve datos;
Ivan
@Ivan: Ah, gracias por ese caso extremo, aunque semánticamente en realidad sería necesario tener una representación adicional para los objetos vacíos. Pero no, result === datano funcionará, nunca son idénticos.
Bergi
@ Bergi Sí, tienes razón. Object.keys (data) .length === 0 funciona aunque
Ivan
26

Escribí dos funciones flatteny unflattenun objeto JSON.


Acoplar un objeto JSON :

var flatten = (function (isArray, wrapped) {
    return function (table) {
        return reduce("", {}, table);
    };

    function reduce(path, accumulator, table) {
        if (isArray(table)) {
            var length = table.length;

            if (length) {
                var index = 0;

                while (index < length) {
                    var property = path + "[" + index + "]", item = table[index++];
                    if (wrapped(item) !== item) accumulator[property] = item;
                    else reduce(property, accumulator, item);
                }
            } else accumulator[path] = table;
        } else {
            var empty = true;

            if (path) {
                for (var property in table) {
                    var item = table[property], property = path + "." + property, empty = false;
                    if (wrapped(item) !== item) accumulator[property] = item;
                    else reduce(property, accumulator, item);
                }
            } else {
                for (var property in table) {
                    var item = table[property], empty = false;
                    if (wrapped(item) !== item) accumulator[property] = item;
                    else reduce(property, accumulator, item);
                }
            }

            if (empty) accumulator[path] = table;
        }

        return accumulator;
    }
}(Array.isArray, Object));

Rendimiento :

  1. Es más rápido que la solución actual en Opera. La solución actual es un 26% más lenta en Opera.
  2. Es más rápido que la solución actual en Firefox. La solución actual es un 9% más lenta en Firefox.
  3. Es más rápido que la solución actual en Chrome. La solución actual es un 29% más lenta en Chrome.

Desplegar un objeto JSON :

function unflatten(table) {
    var result = {};

    for (var path in table) {
        var cursor = result, length = path.length, property = "", index = 0;

        while (index < length) {
            var char = path.charAt(index);

            if (char === "[") {
                var start = index + 1,
                    end = path.indexOf("]", start),
                    cursor = cursor[property] = cursor[property] || [],
                    property = path.slice(start, end),
                    index = end + 1;
            } else {
                var cursor = cursor[property] = cursor[property] || {},
                    start = char === "." ? index + 1 : index,
                    bracket = path.indexOf("[", start),
                    dot = path.indexOf(".", start);

                if (bracket < 0 && dot < 0) var end = index = length;
                else if (bracket < 0) var end = index = dot;
                else if (dot < 0) var end = index = bracket;
                else var end = index = bracket < dot ? bracket : dot;

                var property = path.slice(start, end);
            }
        }

        cursor[property] = table[path];
    }

    return result[""];
}

Rendimiento :

  1. Es más rápido que la solución actual en Opera. La solución actual es un 5% más lenta en Opera.
  2. Es más lento que la solución actual en Firefox. Mi solución es un 26% más lenta en Firefox.
  3. Es más lento que la solución actual en Chrome. Mi solución es un 6% más lenta en Chrome.

Acoplar y desacoplar un objeto JSON :

En general, mi solución funciona igual de bien o incluso mejor que la solución actual.

Rendimiento :

  1. Es más rápido que la solución actual en Opera. La solución actual es un 21% más lenta en Opera.
  2. Es tan rápido como la solución actual en Firefox.
  3. Es más rápido que la solución actual en Firefox. La solución actual es un 20% más lenta en Chrome.

Formato de salida :

Un objeto aplanado usa la notación de puntos para las propiedades del objeto y la notación de paréntesis para los índices de matriz:

  1. {foo:{bar:false}} => {"foo.bar":false}
  2. {a:[{b:["c","d"]}]} => {"a[0].b[0]":"c","a[0].b[1]":"d"}
  3. [1,[2,[3,4],5],6] => {"[0]":1,"[1][0]":2,"[1][1][0]":3,"[1][1][1]":4,"[1][2]":5,"[2]":6}

En mi opinión, este formato es mejor que solo usar la notación de puntos:

  1. {foo:{bar:false}} => {"foo.bar":false}
  2. {a:[{b:["c","d"]}]} => {"a.0.b.0":"c","a.0.b.1":"d"}
  3. [1,[2,[3,4],5],6] => {"0":1,"1.0":2,"1.1.0":3,"1.1.1":4,"1.2":5,"2":6}

Ventajas :

  1. Acoplar un objeto es más rápido que la solución actual.
  2. Allanar y allanar un objeto es tan rápido o más rápido que la solución actual.
  3. Los objetos aplanados usan tanto la notación de puntos como la notación de corchetes para facilitar la lectura.

desventajas :

  1. Desplazar un objeto es más lento que la solución actual en la mayoría de los casos (pero no en todos).

La demostración actual de JSFiddle dio los siguientes valores como salida:

Nested : 132175 : 63
Flattened : 132175 : 564
Nested : 132175 : 54
Flattened : 132175 : 508

Mi demo JSFiddle actualizada dio los siguientes valores como salida:

Nested : 132175 : 59
Flattened : 132175 : 514
Nested : 132175 : 60
Flattened : 132175 : 451

No estoy realmente seguro de lo que eso significa, así que me quedaré con los resultados de jsPerf. Después de todo, jsPerf es una utilidad de evaluación comparativa de rendimiento. JSFiddle no lo es.

Aadit M Shah
fuente
Muy genial. Realmente me gusta el estilo para aplanar, usando funciones anónimas para obtener Array.isArray y Object en un alcance más cercano. Sin embargo, creo que el objeto de prueba que estás usando para la prueba JSPerf es demasiado simple. Creé el objeto "fillObj ({}, 4)" en mi punto de referencia jsfiddle para emular un caso real de un gran conjunto de datos anidados complejos.
Louis Ricci
Muéstrame el código de tu objeto y lo incorporaré al punto de referencia.
Aadit M Shah
2
@LastCoder Hmmm, su implementación actual parece ser más rápida que la mía en la mayoría de los navegadores (especialmente Firefox). Curiosamente, mi implementación es más rápida en Opera y tampoco es tan mala en Chrome. No creo que tener un conjunto de datos tan grande sea un factor ideal para determinar la velocidad del algoritmo porque: 1) los conjuntos de datos grandes necesitan una gran cantidad de memoria, intercambio de páginas, etc .; y eso no es algo que pueda controlar en JS (es decir, está a merced del navegador) 2) si desea hacer un trabajo intensivo de la CPU, entonces JS no es el mejor lenguaje. Considere usar C en su lugar. Hay bibliotecas JSON para C
Aadit M Shah
1
ese es un buen punto y saca a relucir la diferencia entre el benchmarking sintético y el real. Estoy contento con el rendimiento del JS optimizado actual, por lo que no es necesario usar C.
Louis Ricci
Esta implementación también tiene un prototipo de error de contaminación, por ejemplounflatten({"foo.__proto__.bar": 42})
Alex Brasetvik
12

3 ½ años después ...

Para mi propio proyecto, quería aplanar objetos JSON en notación de puntos mongoDB y se me ocurrió una solución simple:

/**
 * Recursively flattens a JSON object using dot notation.
 *
 * NOTE: input must be an object as described by JSON spec. Arbitrary
 * JS objects (e.g. {a: () => 42}) may result in unexpected output.
 * MOREOVER, it removes keys with empty objects/arrays as value (see
 * examples bellow).
 *
 * @example
 * // returns {a:1, 'b.0.c': 2, 'b.0.d.e': 3, 'b.1': 4}
 * flatten({a: 1, b: [{c: 2, d: {e: 3}}, 4]})
 * // returns {a:1, 'b.0.c': 2, 'b.0.d.e.0': true, 'b.0.d.e.1': false, 'b.0.d.e.2.f': 1}
 * flatten({a: 1, b: [{c: 2, d: {e: [true, false, {f: 1}]}}]})
 * // return {a: 1}
 * flatten({a: 1, b: [], c: {}})
 *
 * @param obj item to be flattened
 * @param {Array.string} [prefix=[]] chain of prefix joined with a dot and prepended to key
 * @param {Object} [current={}] result of flatten during the recursion
 *
 * @see https://docs.mongodb.com/manual/core/document/#dot-notation
 */
function flatten (obj, prefix, current) {
  prefix = prefix || []
  current = current || {}

  // Remember kids, null is also an object!
  if (typeof (obj) === 'object' && obj !== null) {
    Object.keys(obj).forEach(key => {
      this.flatten(obj[key], prefix.concat(key), current)
    })
  } else {
    current[prefix.join('.')] = obj
  }

  return current
}

Características y / o advertencias

  • Solo acepta objetos JSON. Entonces, si pasa algo así, {a: () => {}}¡podría no obtener lo que quería!
  • Elimina matrices y objetos vacíos. Entonces esto {a: {}, b: []}se aplana {}.
Yan Foto
fuente
1
Bien, pero no me encargo de las citas escapadas. Entonces se {"x": "abc\"{x}\"yz"}convierte en lo { "x": "abc"{,"x",}"yz"}que no es válido.
Simsteve7
@ Simsteve7 tienes razón! ¡Algo que siempre tiendo a olvidar!
Yan Foto
11

Versión ES6:

const flatten = (obj, path = '') => {        
    if (!(obj instanceof Object)) return {[path.replace(/\.$/g, '')]:obj};

    return Object.keys(obj).reduce((output, key) => {
        return obj instanceof Array ? 
             {...output, ...flatten(obj[key], path +  '[' + key + '].')}:
             {...output, ...flatten(obj[key], path + key + '.')};
    }, {});
}

Ejemplo:

console.log(flatten({a:[{b:["c","d"]}]}));
console.log(flatten([1,[2,[3,4],5],6]));
Chico
fuente
1
Creo que tendría alguna dificultad para Desaplanar si no tiene separadores entre los nombres de propiedad JSON.stringify (flatten ({"prop1": 0, "prop2": {"prop3": true, "prop4": "test "}})); ==> {"prop1": 0, "prop2prop3": verdadero, "prop2prop4": "test"} pero es una solución fácil, la brevedad de la sintaxis ES6 es realmente agradable
Louis Ricci el
Eso es muy cierto, agregaron separadores
Guy
Esto no funciona bien Date, ¿alguna idea de cómo hacerlo? Por ejemplo, conflatten({a: {b: new Date()}});
Ehtesh Choudhury
Puede usar marcas de tiempo: {b: new Date (). GetTime ()}} y luego devolverlo a la fecha con una nueva Fecha (marca de tiempo)
Guy
6

Aquí hay otro enfoque que funciona más lento (aproximadamente 1000 ms) que la respuesta anterior, pero tiene una idea interesante :-)

En lugar de recorrer cada cadena de propiedades, solo selecciona la última propiedad y usa una tabla de búsqueda para que el resto almacene los resultados intermedios. Esta tabla de búsqueda se repetirá hasta que no queden cadenas de propiedades y todos los valores residan en propiedades no codificadas.

JSON.unflatten = function(data) {
    "use strict";
    if (Object(data) !== data || Array.isArray(data))
        return data;
    var regex = /\.?([^.\[\]]+)$|\[(\d+)\]$/,
        props = Object.keys(data),
        result, p;
    while(p = props.shift()) {
        var m = regex.exec(p),
            target;
        if (m.index) {
            var rest = p.slice(0, m.index);
            if (!(rest in data)) {
                data[rest] = m[2] ? [] : {};
                props.push(rest);
            }
            target = data[rest];
        } else {
            target = result || (result = (m[2] ? [] : {}));
        }
        target[m[2] || m[1]] = data[p];
    }
    return result;
};

Actualmente usa el dataparámetro de entrada para la tabla, y pone muchas propiedades en él; también debería ser posible una versión no destructiva. Tal vez un lastIndexOfuso inteligente funciona mejor que la expresión regular (depende del motor de expresión regular).

Véalo en acción aquí .

Bergi
fuente
No rechacé tu respuesta. Sin embargo, me gustaría señalar que su función no es unflattenel objeto aplanado correctamente. Por ejemplo, considere la matriz [1,[2,[3,4],5],6]. Su flattenfunción aplana este objeto a {"[0]":1,"[1][0]":2,"[1][1][0]":3,"[1][1][1]":4,"[1][2]":5,"[2]":6}. unflattenSin embargo, su función desaplana incorrectamente el objeto aplanado [1,[null,[3,4]],6]. La razón por la que esto sucede se debe a la declaración delete data[p]que elimina prematuramente el valor intermedio [2,null,5]antes de [3,4]agregarlo. Usa una pila para resolverlo. :-)
Aadit M Shah
1
Ah, ya veo, orden de enumeración indefinido ... Voy a arreglarlo con una cola de propiedades, por favor ponga su solución de pila en una respuesta propia. ¡Gracias por la pista!
Bergi
4

Puedes usar https://github.com/hughsk/flat

Tome un objeto Javascript anidado y aplánelo, o aplanar un objeto con claves delimitadas.

Ejemplo del documento

var flatten = require('flat')

flatten({
    key1: {
        keyA: 'valueI'
    },
    key2: {
        keyB: 'valueII'
    },
    key3: { a: { b: { c: 2 } } }
})

// {
//   'key1.keyA': 'valueI',
//   'key2.keyB': 'valueII',
//   'key3.a.b.c': 2
// }


var unflatten = require('flat').unflatten

unflatten({
    'three.levels.deep': 42,
    'three.levels': {
        nested: true
    }
})

// {
//     three: {
//         levels: {
//             deep: 42,
//             nested: true
//         }
//     }
// }
Tom Esterez
fuente
1
¿Cómo se usa esto en AngularJS?
kensplanet
2

Este código recursivamente aplana los objetos JSON.

Incluí mi mecanismo de sincronización en el código y me da 1 ms, pero no estoy seguro de si ese es el más preciso.

            var new_json = [{
              "name": "fatima",
              "age": 25,
              "neighbour": {
                "name": "taqi",
                "location": "end of the street",
                "property": {
                  "built in": 1990,
                  "owned": false,
                  "years on market": [1990, 1998, 2002, 2013],
                  "year short listed": [], //means never
                }
              },
              "town": "Mountain View",
              "state": "CA"
            },
            {
              "name": "qianru",
              "age": 20,
              "neighbour": {
                "name": "joe",
                "location": "opposite to the park",
                "property": {
                  "built in": 2011,
                  "owned": true,
                  "years on market": [1996, 2011],
                  "year short listed": [], //means never
                }
              },
              "town": "Pittsburgh",
              "state": "PA"
            }]

            function flatten(json, flattened, str_key) {
                for (var key in json) {
                  if (json.hasOwnProperty(key)) {
                    if (json[key] instanceof Object && json[key] != "") {
                      flatten(json[key], flattened, str_key + "." + key);
                    } else {
                      flattened[str_key + "." + key] = json[key];
                    }
                  }
                }
            }

        var flattened = {};
        console.time('flatten'); 
        flatten(new_json, flattened, "");
        console.timeEnd('flatten');

        for (var key in flattened){
          console.log(key + ": " + flattened[key]);
        }

Salida:

flatten: 1ms
.0.name: fatima
.0.age: 25
.0.neighbour.name: taqi
.0.neighbour.location: end of the street
.0.neighbour.property.built in: 1990
.0.neighbour.property.owned: false
.0.neighbour.property.years on market.0: 1990
.0.neighbour.property.years on market.1: 1998
.0.neighbour.property.years on market.2: 2002
.0.neighbour.property.years on market.3: 2013
.0.neighbour.property.year short listed: 
.0.town: Mountain View
.0.state: CA
.1.name: qianru
.1.age: 20
.1.neighbour.name: joe
.1.neighbour.location: opposite to the park
.1.neighbour.property.built in: 2011
.1.neighbour.property.owned: true
.1.neighbour.property.years on market.0: 1996
.1.neighbour.property.years on market.1: 2011
.1.neighbour.property.year short listed: 
.1.town: Pittsburgh
.1.state: PA
sfrizvi6
fuente
1
Creo que eso typeof some === 'object'es más rápido, some instanceof Objectya que la primera verificación se realiza en O1, mientras que la segunda en On, donde n es la longitud de una cadena de herencia (el objeto siempre será el último allí).
GullerYA
1

Agregué +/- 10-15% de eficiencia a la respuesta seleccionada mediante una refactorización de código menor y moviendo la función recursiva fuera del espacio de nombres de la función.

Vea mi pregunta: ¿Se reevalúan las funciones de espacio de nombres en cada llamada? por qué esto ralentiza las funciones anidadas.

function _flatten (target, obj, path) {
  var i, empty;
  if (obj.constructor === Object) {
    empty = true;
    for (i in obj) {
      empty = false;
      _flatten(target, obj[i], path ? path + '.' + i : i);
    }
    if (empty && path) {
      target[path] = {};
    }
  } 
  else if (obj.constructor === Array) {
    i = obj.length;
    if (i > 0) {
      while (i--) {
        _flatten(target, obj[i], path + '[' + i + ']');
      }
    } else {
      target[path] = [];
    }
  }
  else {
    target[path] = obj;
  }
}

function flatten (data) {
  var result = {};
  _flatten(result, data, null);
  return result;
}

Ver punto de referencia .

jtrumbull
fuente
1

Aquí está el mío. Se ejecuta en <2 ms en Google Apps Script en un objeto de tamaño considerable. Utiliza guiones en lugar de puntos para los separadores, y no maneja matrices especialmente como en la pregunta del autor de la pregunta, pero esto es lo que quería para mi uso.

function flatten (obj) {
  var newObj = {};
  for (var key in obj) {
    if (typeof obj[key] === 'object' && obj[key] !== null) {
      var temp = flatten(obj[key])
      for (var key2 in temp) {
        newObj[key+"-"+key2] = temp[key2];
      }
    } else {
      newObj[key] = obj[key];
    }
  }
  return newObj;
}

Ejemplo:

var test = {
  a: 1,
  b: 2,
  c: {
    c1: 3.1,
    c2: 3.2
  },
  d: 4,
  e: {
    e1: 5.1,
    e2: 5.2,
    e3: {
      e3a: 5.31,
      e3b: 5.32
    },
    e4: 5.4
  },
  f: 6
}

Logger.log("start");
Logger.log(JSON.stringify(flatten(test),null,2));
Logger.log("done");

Salida de ejemplo:

[17-02-08 13:21:05:245 CST] start
[17-02-08 13:21:05:246 CST] {
  "a": 1,
  "b": 2,
  "c-c1": 3.1,
  "c-c2": 3.2,
  "d": 4,
  "e-e1": 5.1,
  "e-e2": 5.2,
  "e-e3-e3a": 5.31,
  "e-e3-e3b": 5.32,
  "e-e4": 5.4,
  "f": 6
}
[17-02-08 13:21:05:247 CST] done
paulwal222
fuente
1

Usa esta biblioteca:

npm install flat

Uso (de https://www.npmjs.com/package/flat ):

Aplanar:

    var flatten = require('flat')


    flatten({
        key1: {
            keyA: 'valueI'
        },
        key2: {
            keyB: 'valueII'
        },
        key3: { a: { b: { c: 2 } } }
    })

    // {
    //   'key1.keyA': 'valueI',
    //   'key2.keyB': 'valueII',
    //   'key3.a.b.c': 2
    // }

Sin aplanar:

var unflatten = require('flat').unflatten

unflatten({
    'three.levels.deep': 42,
    'three.levels': {
        nested: true
    }
})

// {
//     three: {
//         levels: {
//             deep: 42,
//             nested: true
//         }
//     }
// }
onz
fuente
2
Para completar su respuesta, debe agregar un ejemplo de cómo usar esa biblioteca.
António Almeida
0

Me gustaría agregar una nueva versión de flatten case (esto es lo que necesitaba :)) que, según mis sondas con el jsFiddler anterior, es un poco más rápido que el seleccionado actualmente. Además, personalmente veo este fragmento un poco más legible, lo que, por supuesto, es importante para proyectos de múltiples desarrolladores.

function flattenObject(graph) {
    let result = {},
        item,
        key;

    function recurr(graph, path) {
        if (Array.isArray(graph)) {
            graph.forEach(function (itm, idx) {
                key = path + '[' + idx + ']';
                if (itm && typeof itm === 'object') {
                    recurr(itm, key);
                } else {
                    result[key] = itm;
                }
            });
        } else {
            Reflect.ownKeys(graph).forEach(function (p) {
                key = path + '.' + p;
                item = graph[p];
                if (item && typeof item === 'object') {
                    recurr(item, key);
                } else {
                    result[key] = item;
                }
            });
        }
    }
    recurr(graph, '');

    return result;
}
GullerYA
fuente
0

Aquí hay un código que escribí para aplanar un objeto con el que estaba trabajando. Crea una nueva clase que toma cada campo anidado y lo lleva a la primera capa. Puede modificarlo para que no se aplaste recordando la ubicación original de las teclas. También supone que las claves son únicas incluso en los objetos anidados. Espero eso ayude.

class JSONFlattener {
    ojson = {}
    flattenedjson = {}

    constructor(original_json) {
        this.ojson = original_json
        this.flattenedjson = {}
        this.flatten()
    }

    flatten() {
        Object.keys(this.ojson).forEach(function(key){
            if (this.ojson[key] == null) {

            } else if (this.ojson[key].constructor == ({}).constructor) {
                this.combine(new JSONFlattener(this.ojson[key]).returnJSON())
            } else {
                this.flattenedjson[key] = this.ojson[key]
            }
        }, this)        
    }

    combine(new_json) {
        //assumes new_json is a flat array
        Object.keys(new_json).forEach(function(key){
            if (!this.flattenedjson.hasOwnProperty(key)) {
                this.flattenedjson[key] = new_json[key]
            } else {
                console.log(key+" is a duplicate key")
            }
        }, this)
    }

    returnJSON() {
        return this.flattenedjson
    }
}

console.log(new JSONFlattener(dad_dictionary).returnJSON())

Como ejemplo, convierte

nested_json = {
    "a": {
        "b": {
            "c": {
                "d": {
                    "a": 0
                }
            }
        }
    },
    "z": {
        "b":1
    },
    "d": {
        "c": {
            "c": 2
        }
    }
}

dentro

{ a: 0, b: 1, c: 2 }
Imran Q
fuente