A maneira mais rápida de nivelar / remover nivelamentos de objetos JSON aninhados

159

Joguei um código juntos para achatar e descompactar objetos JSON complexos / aninhados. Funciona, mas é um pouco lento (aciona o aviso de 'script longo').

Para os nomes achatados, eu quero "." como o delimitador e [INDEX] para matrizes.

Exemplos:

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}

Criei uma referência que ~ simula meu caso de uso http://jsfiddle.net/WSzec/

  • Obter um objeto JSON aninhado
  • Achatá-lo
  • Examine-o e possivelmente modifique-o enquanto achatado
  • Abra-o novamente para o formato aninhado original para ser enviado

Gostaria de um código mais rápido: para esclarecer, o código que completa o benchmark JSFiddle ( http://jsfiddle.net/WSzec/ ) significativamente mais rápido (~ 20% + seria bom) no IE 9+, FF 24+ e Chrome 29 +.

Aqui está o código JavaScript relevante: Atual mais rápida: 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 Modificado acima para a implementação da @Bergi, que atualmente é a mais rápida. Além disso, o uso de ".indexOf" em vez de "regex.exec" é cerca de 20% mais rápido no FF, mas 20% mais lento no Chrome; portanto, continuarei com o regex, pois é mais simples (aqui está minha tentativa de usar o indexOf para substituir o regex http://jsfiddle.net/WSzec/2/ ).

EDIT 2 Com base na ideia do @Bergi, consegui criar uma versão não-regex mais rápida (3x mais rápida no FF e ~ 10% mais rápida no Chrome). http://jsfiddle.net/WSzec/6/ Nesta implementação (atual), as regras para nomes de chaves são simples: as chaves não podem começar com um número inteiro ou conter um período.

Exemplo:

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

EDIT 3 A adição da abordagem de análise de caminho em linha do @AaditMShah (em vez de String.split) ajudou a melhorar o desempenho não uniforme. Estou muito feliz com a melhoria geral de desempenho alcançada.

Os últimos jsfiddle e jsperf:

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

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

Louis Ricci
fonte
7
Não existe um "objeto JSON" . A pergunta parece ser sobre objetos JS.
Felix Kling
1
Esta questão parece ser mais apropriado para o site do Code Review Stackexchange: codereview.stackexchange.com
Aadit M Shah
6
@FelixKling - Por objeto JSON, eu quis dizer objetos JS que contêm apenas tipos JavaScript primitivos. Você poderia, por exemplo, colocar uma função em um objeto JS, mas ele não seria serializado no JSON - ie JSON.stringify ({fn: function () {alert ('a');}}); -
Louis Ricci
2
[1].[1].[0]parece errado para mim. Tem certeza de que este é o resultado desejado?
Bergi
2
Infelizmente, há um erro: os objetos de data são convertidos em um JSON vazio.
giacecco

Respostas:

217

Aqui está minha implementação muito mais curta:

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ão mudou muito (e não tenho certeza se você realmente precisa desses 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, eles executam seu benchmark na metade do tempo (Opera 12.16: ~ 900ms em vez de ~ 1900ms, Chrome 29: ~ 800ms em vez de ~ 1600ms).

Nota: Esta e a maioria das outras soluções respondidas aqui concentram-se na velocidade e são suscetíveis à poluição do protótipo e o shold não deve ser usado em objetos não confiáveis.

Bergi
fonte
1
Isso é ótimo! O regex roda notavelmente bem (especialmente no Chrome), tentei substituí-lo pela lógica indexOf, mas só consegui acelerar o FF. Estarei adicionando uma recompensa a esta pergunta para ver se outra melhoria inteligente pode ser despertada, mas até agora isso é mais do que eu esperava.
Louis Ricci
1
Consegui reduzir a velocidade de sua implementação, substituindo regex.exec () por string.split () e simplificando o formato da chave. Darei alguns dias antes de lhe conceder os pts, mas acho que o 'muro da otimização significativa' foi atingido.
Louis Ricci
JSON.flatten ({}); // {'': {}} - você pode adicionar uma linha após var result = {}; - se (resultado === dados) retornar dados;
Ivan
@ Ivan: Ah, obrigado por esse caso de borda, embora semanticamente seria realmente necessário ter uma representação extra para objetos vazios. Mas não, result === datanão vai funcionar, eles nunca são idênticos.
21913 Bergi
@ Bergi Sim, você está certo. Object.keys (data) .length === 0 funciona embora
Ivan
26

Eu escrevi duas funções flattene unflattenum objeto JSON.


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

Desempenho :

  1. É mais rápido que a solução atual no Opera. A solução atual é 26% mais lenta no Opera.
  2. É mais rápido que a solução atual no Firefox. A solução atual é 9% mais lenta no Firefox.
  3. É mais rápido que a solução atual no Chrome. A solução atual é 29% mais lenta no Chrome.

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

Desempenho :

  1. É mais rápido que a solução atual no Opera. A solução atual é 5% mais lenta no Opera.
  2. É mais lento que a solução atual no Firefox. Minha solução é 26% mais lenta no Firefox.
  3. É mais lento que a solução atual no Chrome. Minha solução é 6% mais lenta no Chrome.

Achatar e desatar um objeto JSON :

No geral, minha solução tem um desempenho igualmente bom ou até melhor do que a solução atual.

Desempenho :

  1. É mais rápido que a solução atual no Opera. A solução atual é 21% mais lenta no Opera.
  2. É tão rápido quanto a solução atual no Firefox.
  3. É mais rápido que a solução atual no Firefox. A solução atual é 20% mais lenta no Chrome.

Formato de saída :

Um objeto nivelado usa a notação de ponto para propriedades do objeto e a notação de colchete para í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}

Na minha opinião, este formato é melhor do que usar apenas a notação de ponto:

  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}

Vantagens :

  1. O achatamento de um objeto é mais rápido que a solução atual.
  2. O achatamento e desatamento de um objeto são tão rápidos quanto ou mais rápidos que a solução atual.
  3. Objetos achatados usam a notação de ponto e a colchete para facilitar a leitura.

Desvantagens :

  1. A desatenção de um objeto é mais lenta que a solução atual na maioria dos casos (mas não em todos).

A demonstração atual do JSFiddle forneceu os seguintes valores como saída:

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

Minha demo atualizada do JSFiddle forneceu os seguintes valores como saída:

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

Não tenho muita certeza do que isso significa, portanto, continuarei com os resultados do jsPerf. Afinal, o jsPerf é um utilitário de benchmarking de desempenho. JSFiddle não é.

Aadit M Shah
fonte
Muito legal. Eu realmente gosto do estilo de achatar, usando funções anônimas para obter Array.isArray e Object em um escopo mais próximo. Eu acho que o objeto de teste que você está usando para o teste JSPerf é muito simples. Criei o objeto "fillObj ({}, 4)" no meu benchmark jsfiddle para emular um caso real de um grande e complexo dado aninhado.
Louis Ricci
Mostre-me o código do seu objeto e eu o incorporarei no benchmark.
Aadit M Shah
2
@LastCoder Hmmm, sua implementação atual parece ser mais rápida que a minha na maioria dos navegadores (principalmente o Firefox). Curiosamente, minha implementação é mais rápida no Opera e também não é tão ruim no Chrome. Eu não acho que ter um conjunto de dados tão grande seja um fator ideal para determinar a velocidade do algoritmo porque: 1) conjuntos de dados grandes precisam de uma grande quantidade de memória, troca de páginas, etc .; e isso não é algo que você pode controlar em JS (ou seja, você está à mercê do navegador) 2) se quiser fazer um trabalho intensivo de CPU, então JS não é a melhor linguagem. Considere usar C em vez disso. Existem bibliotecas JSON para C
Aadit M Shah
1
esse é um bom ponto e traz à tona a diferença entre o benchmarking sintético e o real. Estou feliz com o desempenho do atual otimizado JS, então não há necessidade de usar C.
Louis Ricci
Esta implementação também possui um bug protótipo de poluição, por exemplounflatten({"foo.__proto__.bar": 42})
Alex Brasetvik 19/02
12

3 anos e meio depois ...

Para o meu próprio projeto, eu queria achatar objetos JSON na notação de ponto mongoDB e criei uma solução simples:

/**
 * 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 e / ou advertências

  • Ele aceita apenas objetos JSON. Então, se você passar algo como {a: () => {}}você pode não conseguir o que queria!
  • Remove matrizes e objetos vazios. Então, isso {a: {}, b: []}é achatado {}.
Yan Foto
fonte
1
Legal, mas eu não cuido de citações de escape. Assim, {"x": "abc\"{x}\"yz"}torna-se o { "x": "abc"{,"x",}"yz"}que não é válido.
Simsteve7
@ Simsteve7 você está certo! Algo que eu sempre costumo esquecer!
23619 Yan Foto
11

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

Exemplo:

console.log(flatten({a:[{b:["c","d"]}]}));
console.log(flatten([1,[2,[3,4],5],6]));
Cara
fonte
1
Eu acho que você teria alguma dificuldade em desatualizar se você não tiver separadores entre os nomes de propriedade JSON.stringify (flatten ({"prop1": 0, "prop2": {"prop3": true, "prop4": "test "}})); ==> { "prop1": 0 "prop2prop3": true "prop2prop4": "teste"} , mas, mas é um reparo fácil, a brevidade da sintaxe ES6 é muito bom
Louis Ricci
Isso é muito verdadeiro, separadores acrescentou
Guy
Isso não funciona muito bem Date, alguma idéia de como fazer isso? Por exemplo, comflatten({a: {b: new Date()}});
Ehtesh Choudhury 22/09
Você pode usar timestamps: {b: new Date () getTime ().}} E depois devolvê-lo atualizado com nova data (timestamp)
Guy
6

Aqui está outra abordagem que é mais lenta (cerca de 1000ms) do que a resposta acima, mas tem uma ideia interessante :-)

Em vez de iterar através de cada cadeia de propriedades, apenas escolhe a última propriedade e usa uma tabela de consulta para o restante para armazenar os resultados intermediários. Essa tabela de consulta será iterada até que não haja mais cadeias de propriedades e todos os valores residam em propriedades não localizadas.

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

Atualmente, ele usa o dataparâmetro de entrada para a tabela e coloca muitas propriedades nela - uma versão não destrutiva também deve ser possível. Talvez um lastIndexOfuso inteligente tenha um desempenho melhor que o regex (depende do mecanismo de regex).

Veja em ação aqui .

Bergi
fonte
Não diminuí a votação da sua resposta. No entanto, gostaria de salientar que sua função não faz unflatteno objeto achatado corretamente. Por exemplo, considere a matriz [1,[2,[3,4],5],6]. Sua flattenfunção achata esse objeto {"[0]":1,"[1][0]":2,"[1][1][0]":3,"[1][1][1]":4,"[1][2]":5,"[2]":6}. Sua unflattenfunção, no entanto, incorretamente desatende o objeto achatado [1,[null,[3,4]],6]. A razão pela qual isso acontece é devido à declaração delete data[p]que exclui prematuramente o valor intermediário [2,null,5]antes de [3,4]ser adicionada a ele. Use uma pilha para resolvê-lo. :-)
Aadit M Shah
1
Ah, entendo, ordem de enumeração indefinida ... Vou consertar isso com uma fila de propriedades, por favor, coloque sua solução de pilha em uma resposta própria. Obrigado pela dica!
Bergi 6/10/2013
4

Você pode usar https://github.com/hughsk/flat

Pegue um objeto Javascript aninhado e achatá-lo ou desatere um objeto com chaves delimitadas.

Exemplo do 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
fonte
1
Como você usa isso no AngularJS?
Kensplanet
2

Esse código nivela recursivamente objetos JSON.

Incluí meu mecanismo de temporização no código e ele me dá 1ms, mas não tenho certeza se esse é o mais 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]);
        }

Resultado:

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
fonte
1
Eu acho que isso typeof some === 'object'é mais rápido some instanceof Objectdesde que a primeira verificação seja realizada em O1 e a segunda em On, onde n é o comprimento de uma cadeia de herança (o objeto será sempre o último).
precisa saber é o seguinte
1

Adicionei +/- 10-15% de eficiência à resposta selecionada refatorando um código menor e movendo a função recursiva para fora do espaço de nomes da função.

Veja minha pergunta: as funções com espaço para nome são reavaliadas a cada chamada? por que isso diminui as funções aninhadas.

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

Veja benchmark .

jtrumbull
fonte
1

Aqui está o meu. Ele é executado em <2ms no Script do Google Apps em um objeto considerável. Ele usa traços em vez de pontos para separadores, e não lida com matrizes especialmente como na pergunta do autor, mas é isso que eu queria para o meu 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;
}

Exemplo:

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

Exemplo de saída:

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

Use esta biblioteca:

npm install flat

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

Achatar:

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

Desachatar:

var unflatten = require('flat').unflatten

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

// {
//     three: {
//         levels: {
//             deep: 42,
//             nested: true
//         }
//     }
// }
oz
fonte
2
Para completar sua resposta, você deve adicionar um exemplo de como usar essa biblioteca.
António Almeida
0

Gostaria de adicionar uma nova versão do flatten case (isso é o que eu precisava :)) que, de acordo com minhas análises com o jsFiddler acima, é um pouco mais rápido que o atualmente selecionado. Além disso, eu pessoalmente vejo esse trecho um pouco mais legível, o que é obviamente importante para projetos com vários desenvolvedores.

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

Aqui está um código que escrevi para achatar um objeto com o qual estava trabalhando. Ele cria uma nova classe que pega todos os campos aninhados e os traz para a primeira camada. Você pode modificá-lo para não ficar liso lembrando o posicionamento original das chaves. Ele também assume que as chaves são únicas, mesmo entre objetos aninhados. Espero que ajude.

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 exemplo, ele converte

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

para dentro

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