Le moyen le plus rapide d'aplatir / désaplatir les objets JSON imbriqués

159

J'ai jeté du code ensemble pour aplatir et non aplatir les objets JSON complexes / imbriqués. Cela fonctionne, mais c'est un peu lent (déclenche l'avertissement «long script»).

Pour les noms aplatis que je veux "." comme délimiteur et [INDEX] pour les tableaux.

Exemples:

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}

J'ai créé un benchmark qui ~ simule mon cas d'utilisation http://jsfiddle.net/WSzec/

  • Obtenir un objet JSON imbriqué
  • Aplatir
  • Regardez à travers et modifiez-le éventuellement lorsqu'il est aplati
  • Redressez-le à son format imbriqué d'origine pour qu'il soit expédié

Je voudrais un code plus rapide: pour clarification, un code qui complète le benchmark JSFiddle ( http://jsfiddle.net/WSzec/ ) beaucoup plus rapide (~ 20% + serait bien) dans IE 9+, FF 24+ et Chrome 29 +.

Voici le code JavaScript pertinent: Current Fastest: 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;
}

EDIT 1 Modification de la mise en œuvre de @Bergi qui est actuellement la plus rapide. En passant, l'utilisation de ".indexOf" au lieu de "regex.exec" est environ 20% plus rapide dans FF mais 20% plus lente dans Chrome; je vais donc m'en tenir à l'expression régulière car elle est plus simple (voici ma tentative d'utiliser indexOf pour remplacer l'expression régulière http://jsfiddle.net/WSzec/2/ ).

EDIT 2 En me basant sur l'idée de @Bergi, j'ai réussi à créer une version non-regex plus rapide (3x plus rapide dans FF et ~ 10% plus rapide dans Chrome). http://jsfiddle.net/WSzec/6/ Dans l'implémentation this (l'actuelle), les règles pour les noms de clé sont simplement, les clés ne peuvent pas commencer par un entier ou contenir un point.

Exemple:

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

EDIT 3 L'ajout de l'approche d'analyse de chemin en ligne de @AaditMShah (plutôt que String.split) a contribué à améliorer les performances de mise à plat. Je suis très content de l'amélioration globale des performances atteinte.

Les derniers jsfiddle et jsperf:

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

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

Louis Ricci
la source
7
Il n'existe pas d '"objet JSON" . La question semble concerner les objets JS.
Felix Kling
1
Cette question semble être plus appropriée pour le site Code Review StackExchange: codereview.stackexchange.com
Aadit M Shah
6
@FelixKling - Par objet JSON, je voulais dire des objets JS qui ne contiennent que des types JavaScript primitifs. Vous pourriez, par exemple, mettre une fonction dans un objet JS, mais elle ne serait pas sérialisée en JSON - c'est-à-dire JSON.stringify ({fn: function () {alert ('a');}}); -
Louis Ricci
2
[1].[1].[0]me semble faux. Êtes-vous sûr que c'est le résultat souhaité?
Bergi
2
Il y a malheureusement un bug: les objets de date sont convertis en un JSON vide.
giacecco

Réponses:

217

Voici ma mise en œuvre beaucoup plus courte:

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

flattenn'a pas beaucoup changé (et je ne sais pas si vous avez vraiment besoin de ces isEmptycas):

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

Ensemble, ils exécutent votre benchmark environ la moitié du temps (Opera 12.16: ~ 900ms au lieu de ~ 1900ms, Chrome 29: ~ 800ms au lieu de ~ 1600ms).

Remarque: cette solution et la plupart des autres solutions traitées ici se concentrent sur la vitesse et sont sensibles à la pollution des prototypes et ne doivent pas être utilisées sur des objets non fiables.

Bergi
la source
1
C'est bien! Le regex fonctionne remarquablement bien (en particulier dans Chrome), j'ai essayé de le remplacer par la logique indexOf, mais je n'ai pu réaliser une accélération que dans FF. J'ajouterai une prime à cette question pour voir si une autre amélioration intelligente peut être suscitée, mais jusqu'à présent, c'est plus que ce que j'espérais.
Louis Ricci
1
J'ai réussi à accélérer votre implémentation en remplaçant regex.exec () par string.split () et en simplifiant le format de la clé. Je vais lui donner quelques jours avant de vous attribuer les points, mais je pense que le «mur de l'optimisation significative» a été atteint.
Louis Ricci
JSON.flatten ({}); // {'': {}} - vous pouvez ajouter une ligne après var result = {}; - si (résultat === données) renvoie des données;
Ivan
@Ivan: Ah, merci pour ce cas de bord, mais sémantiquement, il serait en fait nécessaire d'avoir une représentation supplémentaire pour les objets vides. Mais non, result === datacela ne fonctionnera pas, ils ne sont jamais identiques.
Bergi
@Bergi Ouais tu as raison. Object.keys (data) .length === 0 fonctionne bien
Ivan
26

J'ai écrit deux fonctions flattenet unflattenun objet JSON.


Aplatir un objet 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));

Performance :

  1. C'est plus rapide que la solution actuelle d'Opera. La solution actuelle est 26% plus lente dans Opera.
  2. C'est plus rapide que la solution actuelle de Firefox. La solution actuelle est 9% plus lente dans Firefox.
  3. C'est plus rapide que la solution actuelle de Chrome. La solution actuelle est 29% plus lente dans Chrome.

Annuler l'aplatissement d'un objet 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[""];
}

Performance :

  1. C'est plus rapide que la solution actuelle d'Opera. La solution actuelle est 5% plus lente dans Opera.
  2. C'est plus lent que la solution actuelle de Firefox. Ma solution est 26% plus lente dans Firefox.
  3. C'est plus lent que la solution actuelle dans Chrome. Ma solution est 6% plus lente dans Chrome.

Aplatir et aplatir un objet JSON :

Dans l'ensemble, ma solution fonctionne aussi bien ou même mieux que la solution actuelle.

Performance :

  1. C'est plus rapide que la solution actuelle d'Opera. La solution actuelle est 21% plus lente dans Opera.
  2. C'est aussi rapide que la solution actuelle de Firefox.
  3. C'est plus rapide que la solution actuelle de Firefox. La solution actuelle est 20% plus lente dans Chrome.

Format de sortie :

Un objet aplati utilise la notation par points pour les propriétés d'objet et la notation entre crochets pour les indices de tableau:

  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}

À mon avis, ce format est meilleur que d'utiliser uniquement la notation par points:

  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}

Avantages :

  1. L'aplatissement d'un objet est plus rapide que la solution actuelle.
  2. L'aplatissement et l'aplatissement d'un objet sont aussi rapides ou plus rapides que la solution actuelle.
  3. Les objets aplatis utilisent à la fois la notation par points et la notation entre crochets pour la lisibilité.

Inconvénients :

  1. L'aplatissement d'un objet est plus lent que la solution actuelle dans la plupart des cas (mais pas dans tous).

La démo actuelle de JSFiddle a donné les valeurs suivantes en sortie:

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

Ma démo JSFiddle mise à jour a donné les valeurs suivantes en sortie:

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

Je ne suis pas vraiment sûr de ce que cela signifie, donc je vais m'en tenir aux résultats jsPerf. Après tout, jsPerf est un utilitaire d'analyse comparative des performances. JSFiddle ne l'est pas.

Aadit M Shah
la source
Très cool. J'aime vraiment le style d'aplatissement, utilisant des fonctions anonymes pour obtenir Array.isArray et Object dans une portée plus proche. Je pense que l'objet de test que vous utilisez pour le test JSPerf est trop simple. J'ai créé l'objet "fillObj ({}, 4)" dans mon benchmark jsfiddle pour émuler un cas réel d'une grande donnée imbriquée complexe.
Louis Ricci
Montrez-moi le code de votre objet et je l'incorporerai dans le benchmark.
Aadit M Shah
2
@LastCoder Hmmm, votre implémentation actuelle semble être plus rapide que la mienne dans la plupart des navigateurs (notamment Firefox). Fait intéressant, ma mise en œuvre est plus rapide dans Opera et ce n'est pas si mal non plus dans Chrome. Je ne pense pas qu'avoir un si grand ensemble de données soit un facteur idéal pour déterminer la vitesse de l'algorithme car: 1) les grands ensembles de données nécessitent une grande quantité de mémoire, l'échange de pages, etc. et ce n'est pas quelque chose que vous pouvez contrôler dans JS (c'est-à-dire que vous êtes à la merci du navigateur) 2) si vous voulez faire un travail intensif en CPU, JS n'est pas le meilleur langage. Pensez à utiliser C à la place. Il existe des bibliothèques JSON pour C
Aadit M Shah
1
c'est un bon point qui fait la différence entre l'analyse comparative synthétique et réelle. Je suis satisfait des performances du JS optimisé actuel, donc pas besoin d'utiliser C.
Louis Ricci
Cette implémentation a également un prototype de bug de pollution, par exempleunflatten({"foo.__proto__.bar": 42})
Alex Brasetvik
12

3 ans et demi plus tard ...

Pour mon propre projet, je voulais aplatir les objets JSON en notation par points mongoDB et j'ai trouvé une solution 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
}

Caractéristiques et / ou mises en garde

  • Il n'accepte que les objets JSON. Donc, si vous réussissez quelque chose comme {a: () => {}}vous pourriez ne pas obtenir ce que vous vouliez!
  • Il supprime les tableaux et les objets vides. Donc, {a: {}, b: []}c'est aplati {}.
Yan Foto
la source
1
Bien, mais je ne m'occupe pas des citations échappées. Ainsi {"x": "abc\"{x}\"yz"}devient { "x": "abc"{,"x",}"yz"}ce qui est invalide.
Simsteve7
@ Simsteve7 vous avez raison! Quelque chose que j'ai toujours tendance à oublier!
Yan Foto
11

Version 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 + '.')};
    }, {});
}

Exemple:

console.log(flatten({a:[{b:["c","d"]}]}));
console.log(flatten([1,[2,[3,4],5],6]));
Gars
la source
1
Je pense que vous auriez des difficultés à désaplatir si vous n'avez pas de séparateurs entre les noms de propriété JSON.stringify (flatten ({"prop1": 0, "prop2": {"prop3": true, "prop4": "test "}})); ==> {"prop1": 0, "prop2prop3": true, "prop2prop4": "test"} mais mais c'est une solution facile, la brièveté de la syntaxe ES6 est vraiment sympa
Louis Ricci
C'est très vrai, ont ajouté les séparateurs
Guy
Cela ne joue pas très bien avec Date, aucune idée de comment le faire faire? Par exemple, avecflatten({a: {b: new Date()}});
Ehtesh Choudhury
Vous pouvez utiliser les horodatages: {b: new Date (). GetTime ()}} et plus tard le remettre à jour avec une nouvelle date (horodatage)
Guy
6

Voici une autre approche qui fonctionne plus lentement (environ 1000 ms) que la réponse ci-dessus, mais qui a une idée intéressante :-)

Au lieu d'itérer dans chaque chaîne de propriétés, il sélectionne simplement la dernière propriété et utilise une table de consultation pour le reste pour stocker les résultats intermédiaires. Cette table de recherche sera itérée jusqu'à ce qu'il n'y ait plus de chaînes de propriétés et que toutes les valeurs résident sur des propriétés non cataloguées.

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

Il utilise actuellement le dataparamètre d'entrée de la table et y met de nombreuses propriétés - une version non destructive devrait également être possible. Peut-être qu'une lastIndexOfutilisation intelligente fonctionne mieux que le regex (dépend du moteur de regex).

Voyez-le en action ici .

Bergi
la source
Je n'ai pas rejeté votre réponse. Cependant, je tiens à souligner que votre fonction ne permet pas unflattencorrectement à l'objet aplati. Par exemple, considérez le tableau [1,[2,[3,4],5],6]. Votre flattenfonction aplatit cet objet en {"[0]":1,"[1][0]":2,"[1][1][0]":3,"[1][1][1]":4,"[1][2]":5,"[2]":6}. unflattenCependant, votre fonction n'aplatit pas correctement l'objet aplati [1,[null,[3,4]],6]. La raison pour laquelle cela se produit est à cause de l'instruction delete data[p]qui supprime prématurément la valeur intermédiaire [2,null,5]avant d' [3,4]y être ajoutée. Utilisez une pile pour le résoudre. :-)
Aadit M Shah
1
Ah, je vois, ordre d'énumération non défini… Je vais le corriger avec une file de propriétés, veuillez mettre votre solution de pile dans une propre réponse. Merci pour l'indice!
Bergi
4

Vous pouvez utiliser https://github.com/hughsk/flat

Prenez un objet Javascript imbriqué et aplatissez-le, ou aplatissez un objet avec des clés délimitées.

Exemple de la doc

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
la source
1
Comment utilisez-vous cela dans AngularJS?
kensplanet
2

Ce code aplatit de manière récursive les objets JSON.

J'ai inclus mon mécanisme de chronométrage dans le code et cela me donne 1 ms mais je ne suis pas sûr que ce soit le plus précis.

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

Production:

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
la source
1
Je pense que typeof some === 'object'c'est plus rapide some instanceof Objectpuisque le premier contrôle s'effectue en O1 tandis que le second en On où n est une longueur d'une chaîne d'héritage (Object sera toujours le dernier).
GullerYA
1

J'ai ajouté une efficacité de +/- 10 à 15% à la réponse sélectionnée en refactorisant le code mineur et en déplaçant la fonction récursive en dehors de l'espace de noms de la fonction.

Voir ma question: les fonctions d'espacement de noms sont-elles réévaluées à chaque appel? pourquoi cela ralentit les fonctions imbriquées.

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

Voir référence .

jtrumbull
la source
1

Voici la mienne. Il s'exécute en moins de 2 ms dans Google Apps Script sur un objet de grande taille. Il utilise des tirets au lieu de points pour les séparateurs, et il ne gère pas les tableaux spécialement comme dans la question du demandeur, mais c'est ce que je voulais pour mon utilisation.

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

Exemple:

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

Exemple de sortie:

[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
la source
1

Utilisez cette bibliothèque:

npm install flat

Utilisation (depuis https://www.npmjs.com/package/flat ):

Aplatir:

    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
    // }

Désaplatir:

var unflatten = require('flat').unflatten

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

// {
//     three: {
//         levels: {
//             deep: 42,
//             nested: true
//         }
//     }
// }
oz
la source
2
Pour compléter votre réponse, vous devez ajouter un exemple d'utilisation de cette bibliothèque.
António Almeida
0

Je voudrais ajouter une nouvelle version de flatten case (c'est ce dont j'avais besoin :)) qui, selon mes sondes avec le jsFiddler ci-dessus, est légèrement plus rapide que celle actuellement sélectionnée. De plus, je vois personnellement cet extrait de code un peu plus lisible, ce qui est bien sûr important pour les projets multi-développeurs.

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
la source
0

Voici un code que j'ai écrit pour aplatir un objet avec lequel je travaillais. Il crée une nouvelle classe qui prend chaque champ imbriqué et l'amène dans la première couche. Vous pouvez le modifier pour l'aplatir en vous rappelant l'emplacement d'origine des clés. Il suppose également que les clés sont uniques, même entre les objets imbriqués. J'espère que ça aide.

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

À titre d'exemple, il convertit

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

dans

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