Der schnellste Weg, um verschachtelte JSON-Objekte zu reduzieren / zu entfernen

159

Ich habe Code zusammengeschmissen, um komplexe / verschachtelte JSON-Objekte zu reduzieren und zu entfernen. Es funktioniert, ist aber etwas langsam (löst die Warnung "Langes Skript" aus).

Für die abgeflachten Namen möchte ich "." als Trennzeichen und [INDEX] für Arrays.

Beispiele:

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}

Ich habe einen Benchmark erstellt, der meinen Anwendungsfall simuliert. Http://jsfiddle.net/WSzec/

  • Ruft ein verschachteltes JSON-Objekt ab
  • Flache es ab
  • Schauen Sie es durch und ändern Sie es möglicherweise, während es abgeflacht ist
  • Entflachen Sie es wieder auf das ursprüngliche verschachtelte Format, um es zu versenden

Ich möchte schnelleren Code: Zur Verdeutlichung Code, der den JSFiddle-Benchmark ( http://jsfiddle.net/WSzec/ ) in IE 9+, FF 24+ und Chrome 29 deutlich schneller (~ 20% + wäre schön) vervollständigt +.

Hier ist der relevante JavaScript-Code: Aktuell am schnellsten: 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;
}

BEARBEITEN 1 Das oben Gesagte wurde an die Implementierung von @Bergi geändert, die derzeit die schnellste ist. Abgesehen davon ist die Verwendung von ".indexOf" anstelle von "regex.exec" in FF etwa 20% schneller, in Chrome jedoch 20% langsamer. Also bleibe ich bei der Regex, da es einfacher ist (hier ist mein Versuch, indexOf zu verwenden, um die Regex zu ersetzen http://jsfiddle.net/WSzec/2/ ).

EDIT 2 Aufbauend auf der Idee von @Bergi habe ich es geschafft, eine schnellere Nicht-Regex-Version zu erstellen (3x schneller in FF und ~ 10% schneller in Chrome). http://jsfiddle.net/WSzec/6/ In dieser (aktuellen) Implementierung lauten die Regeln für Schlüsselnamen einfach: Schlüssel dürfen nicht mit einer Ganzzahl beginnen oder einen Punkt enthalten.

Beispiel:

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

BEARBEITEN 3BEARBEITEN Durch Hinzufügen des Inline-Pfad-Parsing-Ansatzes von @AaditMShah (anstelle von String.split) konnte die Leistung beim Abflachen verbessert werden. Ich bin sehr zufrieden mit der insgesamt erzielten Leistungsverbesserung.

Die neuesten jsfiddle und jsperf:

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

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

Louis Ricci
quelle
7
Es gibt kein "JSON-Objekt" . Die Frage scheint sich um JS-Objekte zu handeln.
Felix Kling
1
Diese Frage scheint besser geeignet für den Code Review Stack Ort zu sein: codereview.stackexchange.com
Aadit M Shah
6
@FelixKling - Mit JSON-Objekt meine ich JS-Objekte, die nur primitive JavaScript-Typen enthalten. Sie könnten beispielsweise eine Funktion in ein JS-Objekt einfügen, diese würde jedoch nicht in JSON serialisiert - dh JSON.stringify ({fn: function () {alert ('a');}}); -
Louis Ricci
2
[1].[1].[0]sieht für mich falsch aus. Sind Sie sicher, dass dies das gewünschte Ergebnis ist?
Bergi
2
Es gibt leider einen Fehler: Datumsobjekte werden in einen leeren JSON konvertiert.
Giacecco

Antworten:

217

Hier ist meine viel kürzere Implementierung:

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

flattenhat sich nicht viel geändert (und ich bin mir nicht sicher, ob Sie diese isEmptyFälle wirklich brauchen ):

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

Zusammen führen sie Ihren Benchmark in etwa der Hälfte der Zeit aus (Opera 12.16: ~ 900 ms statt ~ 1900 ms, Chrome 29: ~ 800 ms anstelle von ~ 1600 ms).

Hinweis: Diese und die meisten anderen hier beantworteten Lösungen konzentrieren sich auf die Geschwindigkeit und sind anfällig für Prototypenverschmutzung. Sie dürfen nicht für nicht vertrauenswürdige Objekte verwendet werden.

Bergi
quelle
1
Das ist toll! Der reguläre Ausdruck läuft bemerkenswert gut (insbesondere in Chrome). Ich habe versucht, ihn durch die indexOf-Logik zu ersetzen, konnte jedoch nur in FF eine Beschleunigung erzielen. Ich werde dieser Frage ein Kopfgeld hinzufügen, um zu sehen, ob eine weitere clevere Verbesserung erzielt werden kann, aber bisher ist dies mehr als das, was ich mir erhofft hatte.
Louis Ricci
1
Ich habe es geschafft, Ihre Implementierung schneller zu machen, indem ich die Datei regex.exec () durch string.split () ersetzt und das Schlüsselformat vereinfacht habe. Ich werde es ein paar Tage geben, bevor ich Ihnen die Punkte vergebe, aber ich denke, die "Mauer der sinnvollen Optimierung" ist erreicht.
Louis Ricci
JSON.flatten ({}); // {'': {}} - Sie können eine Zeile nach var result = {} hinzufügen. - if (result === data) gibt Daten zurück;
Ivan
@Ivan: Ah, danke für diesen Randfall, obwohl es semantisch tatsächlich erforderlich wäre, eine zusätzliche Darstellung für leere Objekte zu haben. Aber nein, result === datawird nicht funktionieren, sie sind nie identisch.
Bergi
@Bergi Ja du hast recht. Object.keys (data) .length === 0 funktioniert allerdings
Ivan
26

Ich habe zwei Funktionen flattenund unflattenein JSON-Objekt geschrieben.


Reduzieren Sie ein JSON-Objekt :

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

Leistung :

  1. Es ist schneller als die aktuelle Lösung in Opera. Die aktuelle Lösung ist in Opera 26% langsamer.
  2. Es ist schneller als die aktuelle Lösung in Firefox. Die aktuelle Lösung ist in Firefox 9% langsamer.
  3. Es ist schneller als die aktuelle Lösung in Chrome. Die aktuelle Lösung ist in Chrome 29% langsamer.

Entflachen Sie ein JSON-Objekt :

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[""];
}

Leistung :

  1. Es ist schneller als die aktuelle Lösung in Opera. Die aktuelle Lösung ist in Opera 5% langsamer.
  2. Es ist langsamer als die aktuelle Lösung in Firefox. Meine Lösung ist in Firefox 26% langsamer.
  3. Es ist langsamer als die aktuelle Lösung in Chrome. Meine Lösung ist in Chrome 6% langsamer.

Reduzieren und reduzieren Sie ein JSON-Objekt :

Insgesamt ist meine Lösung entweder gleich gut oder sogar besser als die aktuelle Lösung.

Leistung :

  1. Es ist schneller als die aktuelle Lösung in Opera. Die aktuelle Lösung ist in Opera 21% langsamer.
  2. Es ist so schnell wie die aktuelle Lösung in Firefox.
  3. Es ist schneller als die aktuelle Lösung in Firefox. Die aktuelle Lösung ist in Chrome 20% langsamer.

Ausgabeformat :

Ein abgeflachtes Objekt verwendet die Punktnotation für Objekteigenschaften und die Klammernotation für Arrayindizes:

  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}

Meiner Meinung nach ist dieses Format besser als nur die Punktnotation:

  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}

Vorteile :

  1. Das Reduzieren eines Objekts ist schneller als die aktuelle Lösung.
  2. Das Abflachen und Abflachen eines Objekts ist so schnell oder schneller als die aktuelle Lösung.
  3. Abgeflachte Objekte verwenden zur besseren Lesbarkeit sowohl die Punktnotation als auch die Klammernotation.

Nachteile :

  1. Das Abflachen eines Objekts ist in den meisten (aber nicht allen) Fällen langsamer als die aktuelle Lösung.

Die aktuelle JSFiddle-Demo lieferte die folgenden Werte als Ausgabe:

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

Meine aktualisierte JSFiddle-Demo ergab die folgenden Werte als Ausgabe:

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

Ich bin mir nicht sicher, was das bedeutet, also bleibe ich bei den jsPerf-Ergebnissen. Immerhin ist jsPerf ein Dienstprogramm zum Leistungsbenchmarking. JSFiddle ist nicht.

Aadit M Shah
quelle
Sehr cool. Ich mag den Stil für das Reduzieren sehr, indem ich anonyme Funktionen verwende, um Array.isArray und Object näher zusammenzubringen. Ich denke, das Testobjekt, das Sie für den JSPerf-Test verwenden, ist jedoch zu einfach. Ich habe das Objekt "fillObj ({}, 4)" in meinem jsfiddle-Benchmark erstellt, um einen realen Fall eines großen komplexen verschachtelten Datenelements zu emulieren.
Louis Ricci
Zeigen Sie mir den Code für Ihr Objekt und ich werde ihn in den Benchmark integrieren.
Aadit M Shah
2
@LastCoder Hmmm, Ihre aktuelle Implementierung scheint in den meisten Browsern (insbesondere Firefox) schneller zu sein als meine. Interessanterweise ist meine Implementierung in Opera schneller und in Chrome auch nicht so schlecht. Ich denke nicht, dass ein so großer Datensatz ein idealer Faktor ist, um die Geschwindigkeit des Algorithmus zu bestimmen, weil: 1) große Datensätze viel Speicher, Seitentausch usw. benötigen; und das können Sie in JS nicht steuern (dh Sie sind dem Browser ausgeliefert) 2) Wenn Sie CPU-intensive Arbeit leisten möchten, ist JS nicht die beste Sprache. Verwenden Sie stattdessen C. Es gibt JSON-Bibliotheken für C
Aadit M Shah
1
Das ist ein guter Punkt und bringt den Unterschied zwischen synthetischem und realem Benchmarking zum Ausdruck. Ich bin mit der Leistung des aktuell optimierten JS zufrieden, daher muss C. nicht verwendet werden.
Louis Ricci
Diese Implementierung hat auch einen Prototyp eines Verschmutzungsfehlers, z. B.unflatten({"foo.__proto__.bar": 42})
Alex Brasetvik,
12

3 ½ Jahre später ...

Für mein eigenes Projekt wollte ich JSON-Objekte in MongoDB-Punktnotation reduzieren und fand eine einfache Lösung:

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

Merkmale und / oder Einschränkungen

  • Es werden nur JSON-Objekte akzeptiert. Wenn Sie also so etwas bestehen, bekommen {a: () => {}}Sie möglicherweise nicht das, was Sie wollten!
  • Es entfernt leere Arrays und Objekte. Das {a: {}, b: []}ist also abgeflacht {}.
Yan Foto
quelle
1
Schön, aber ich kümmere mich nicht um entkommene Zitate. So {"x": "abc\"{x}\"yz"}wird { "x": "abc"{,"x",}"yz"}was ungültig ist.
Simsteve7
@ Simsteve7 du hast recht! Etwas, das ich immer wieder vergesse!
Yan Foto
11

ES6-Version:

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

Beispiel:

console.log(flatten({a:[{b:["c","d"]}]}));
console.log(flatten([1,[2,[3,4],5],6]));
Kerl
quelle
1
Ich denke, Sie hätten einige Schwierigkeiten beim Abflachen, wenn Sie keine Trennzeichen zwischen den Eigenschaftsnamen JSON.stringify (Abflachen ({"prop1": 0, "prop2": {"prop3": true, "prop4": "test) haben "}})); ==> {"prop1": 0, "prop2prop3": true, "prop2prop4": "test"} , aber es ist eine einfache Lösung, die Kürze der ES6-Syntax ist wirklich nett
Louis Ricci
Das ist sehr wahr, Trennzeichen hinzugefügt
Guy
Das spielt nicht gut mit Date, eine Idee, wie man es dazu bringt ? Zum Beispiel mitflatten({a: {b: new Date()}});
Ehtesh Choudhury
Sie können Zeitstempel verwenden: {b: new Date (). GetTime ()}} und später mit neuem Datum (Zeitstempel) auf das Datum zurücksetzen
Guy
6

Hier ist ein anderer Ansatz, der langsamer läuft (ungefähr 1000 ms) als die obige Antwort, aber eine interessante Idee hat :-)

Anstatt jede Eigenschaftskette zu durchlaufen, wird nur die letzte Eigenschaft ausgewählt und für den Rest eine Nachschlagetabelle verwendet, um die Zwischenergebnisse zu speichern. Diese Nachschlagetabelle wird wiederholt, bis keine Eigenschaftsketten mehr vorhanden sind und sich alle Werte auf nicht verketteten Eigenschaften befinden.

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

Es verwendet derzeit die data Eingabeparameter für die Tabelle verwendet und es werden viele Eigenschaften hinzugefügt. Eine zerstörungsfreie Version sollte ebenfalls möglich sein. Vielleicht ist eine clevere lastIndexOfVerwendung besser als die Regex (hängt von der Regex-Engine ab).

Sehen Sie es hier in Aktion .

Bergi
quelle
Ich habe Ihre Antwort nicht abgelehnt. Ich möchte jedoch darauf hinweisen, dass Ihre Funktion unflattendas abgeflachte Objekt nicht korrekt wiedergibt. Betrachten Sie zum Beispiel das Array [1,[2,[3,4],5],6]. Ihre flattenFunktion glättet dieses Objekt auf {"[0]":1,"[1][0]":2,"[1][1][0]":3,"[1][1][1]":4,"[1][2]":5,"[2]":6}. Ihre unflattenFunktion hebt das abgeflachte Objekt jedoch fälschlicherweise auf [1,[null,[3,4]],6]. Der Grund dafür ist die Anweisung, delete data[p]die den Zwischenwert vorzeitig löscht, [2,null,5]bevor er [3,4]hinzugefügt wird. Verwenden Sie einen Stapel, um es zu lösen. :-)
Aadit M Shah
1
Ah, ich verstehe, undefinierte Aufzählungsreihenfolge ... Um dies mit einer Warteschlange von Eigenschaften zu beheben, geben Sie bitte Ihre Stapellösung in eine eigene Antwort ein. Danke für den Tipp!
Bergi
4

Sie können https://github.com/hughsk/flat verwenden

Nehmen Sie ein verschachteltes Javascript-Objekt und reduzieren Sie es oder entfernen Sie ein Objekt mit begrenzten Schlüsseln.

Beispiel aus dem Dokument

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
quelle
1
Wie benutzt man das in AngularJS?
Kensplanet
2

Dieser Code glättet rekursiv JSON-Objekte.

Ich habe meinen Zeitmechanismus in den Code aufgenommen und er gibt mir 1 ms, aber ich bin mir nicht sicher, ob das der genaueste ist.

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

Ausgabe:

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
quelle
1
Ich denke, das typeof some === 'object'ist schneller als some instanceof Objectda die erste Prüfung in O1 ausgeführt wird, während die zweite in On ausgeführt wird, wobei n eine Länge einer Vererbungskette ist (Objekt wird dort immer das letzte sein).
GullerYA
1

Ich habe der ausgewählten Antwort eine Effizienz von +/- 10-15% hinzugefügt, indem ich den Code umgestaltet und die rekursive Funktion außerhalb des Funktionsnamensraums verschoben habe.

Siehe meine Frage: Werden Namespace-Funktionen bei jedem Aufruf neu bewertet? warum dies verschachtelte Funktionen verlangsamt.

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

Siehe Benchmark .

jtrumbull
quelle
1

Hier ist meins. Es wird in <2 ms in Google Apps Script auf einem großen Objekt ausgeführt. Es werden Bindestriche anstelle von Punkten für Trennzeichen verwendet, und es werden keine Arrays speziell wie in der Frage des Fragestellers behandelt, aber dies ist das, was ich für meine Verwendung wollte.

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

Beispiel:

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

Beispielausgabe:

[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
quelle
1

Verwenden Sie diese Bibliothek:

npm install flat

Verwendung (von https://www.npmjs.com/package/flat ):

Ebnen:

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

Abflachen:

var unflatten = require('flat').unflatten

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

// {
//     three: {
//         levels: {
//             deep: 42,
//             nested: true
//         }
//     }
// }
oz
quelle
2
Um Ihre Antwort zu vervollständigen, sollten Sie ein Beispiel für die Verwendung dieser Bibliothek hinzufügen.
António Almeida
0

Ich möchte eine neue Version von Flatten Case hinzufügen (das ist, was ich brauchte :)), die laut meinen Tests mit dem obigen jsFiddler etwas schneller ist als die aktuell ausgewählte. Außerdem sehe ich persönlich diesen Ausschnitt etwas lesbarer, was natürlich für Projekte mit mehreren Entwicklern wichtig ist.

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
quelle
0

Hier ist ein Code, den ich geschrieben habe, um ein Objekt zu reduzieren, mit dem ich gearbeitet habe. Es wird eine neue Klasse erstellt, die jedes verschachtelte Feld in die erste Ebene bringt. Sie können es so ändern, dass es abgeflacht wird, indem Sie sich an die ursprüngliche Platzierung der Schlüssel erinnern. Es wird auch davon ausgegangen, dass die Schlüssel auch für verschachtelte Objekte eindeutig sind. Ich hoffe es hilft.

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

Als Beispiel konvertiert es

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

in

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