Como dividir objeto em objeto aninhado? (Maneira recursiva)

8

Eu tenho um conjunto de dados contendo o nome da variável sublinhado (_). Como abaixo:

const data = {
   m_name: 'my name',
   m_address: 'my address',
   p_1_category: 'cat 1',
   p_1_name: 'name 1',
   p_2_category: 'cat 2',
   p_2_name: 'name 2'
}

Eu quero dividi-los em objeto aninhado / matriz, abaixo está o resultado que eu quero.

{
 m: {
     name: "my name",
     address: "my address"
 },
 p: {
    "1": {category: 'cat 1', name: 'name 1'}, 
    "2": {category: 'cat 2', name: 'name 2'}
 } 
}

Como posso escrever um método recursivo para obtê-lo em vez de codificado? Talvez deva permitir manipular objetos aninhados mais profundos, como "p_2_one_two_category: 'value'" em p: {2: {one: {two: category: 'value'}}}

var data ={
  m_name: 'my name',
  m_address: 'my address',
  p_1_category: 'cat 1',
  p_1_name: 'name 1',
  p_2_category: 'cat 2',
  p_2_name: 'name 2',
  p_2_contact: '1234567',
  k_id: 111,
  k_name: 'abc'
}

var o ={};
Object.keys(data).forEach(key => {
  var splited =  key.split(/_(.+)/);
  if (!o[splited[0]]) {
    o[splited[0]] = {};
  }
  var splited1 = splited[1].split(/_(.+)/);
  if (splited1.length < 3) {
    o[splited[0]][splited[1]] = data[key];
  } else {
    if (!o[splited[0]][splited1[0]]){ 
      o[splited[0]][splited1[0]] = {};
    }
    o[splited[0]][splited1[0]][splited1[1]] = data[key];
  }
});
console.log(o);

Devb
fonte
Seu código e o código na resposta do Nenad geram algo um pouco diferente da sua saída de amostra. ( mé um objeto simples, não uma matriz de objetos de propriedade única.) Esses resultados parecem muito mais lógicos que a saída de amostra. É isso que você realmente quer?
Scott Sauyet
@ScottSauyet ops, coloquei o resultado errado, atualizei-o, mas meu snippet é o mesmo que ele fez. sim, é isso que eu quero .. Obrigado
Devb

Respostas:

6

Você pode usar o reducemétodo que criará uma estrutura aninhada semelhante com apenas objetos.

var data = {
  m_name: 'my name',
  m_address: 'my address',
  p_1_category: 'cat 1',
  p_1_name: 'name 1',
  p_2_category: 'cat 2',
  p_2_name: 'name 2',
  p_2_contact: '1234567',
  k_id: 111,
  k_name: 'abc'
}


const result = Object
  .entries(data)
  .reduce((a, [k, v]) => {
    k.split('_').reduce((r, e, i, arr) => {
      return r[e] || (r[e] = arr[i + 1] ? {} : v)
    }, a)

    return a
  }, {})

console.log(result)

Nenad Vracar
fonte
2

Não sei se esse formato de saída era o que você realmente estava procurando ou simplesmente o melhor que conseguiu. Uma alternativa seria gerar algo parecido com isto:

{
  m: {name: "my name", address: "my address"},
  p: [
    {category: "cat 1", name: "name 1"},
    {category: "cat 2", name: "name 2"}
  ]
}

Há uma grande diferença entre isso e a saída do seu código. pagora é uma matriz simples de objetos, em vez de um objeto 1- e - 2indexado. É bem possível que isso não seja útil para você, mas é uma alternativa interessante. Há também uma segunda diferença em relação à saída de amostra que você forneceu. O código original e a resposta do Nenad retornam em m: {name: "my name", address: "my address"}vez do solicitado m: [{name: "my name"}, {address: "my address"}]. Isso parece muito mais lógico para mim, e eu também fiz dessa maneira.

Aqui está um código que faria isso:

// Utility functions

const isInt = Number.isInteger

const path = (ps = [], obj = {}) =>
  ps .reduce ((o, p) => (o || {}) [p], obj)

const assoc = (prop, val, obj) => 
  isInt (prop) && Array .isArray (obj)
    ? [... obj .slice (0, prop), val, ...obj .slice (prop + 1)]
    : {...obj, [prop]: val}

const assocPath = ([p = undefined, ...ps], val, obj) => 
  p == undefined
    ? obj
    : ps.length == 0
      ? assoc(p, val, obj)
      : assoc(p, assocPath(ps, val, obj[p] || (obj[p] = isInt(ps[0]) ? [] : {})), obj)

// Main function

const hydrate = (flat) => 
  Object .entries (flat) 
    .map (([k, v]) => [k .split ('_'), v])
    .map (([k, v]) => [k .map (p => isNaN (p) ? p : p - 1), v])
    .reduce ((a, [k, v]) => assocPath (k, v, a), {})

// Test data

const data = {m_name: 'my name', m_address: 'my address', p_1_category: 'cat 1', p_1_name: 'name 1', p_2_category: 'cat 2', p_2_name: 'name 2' }

// Demo

console .log (
  hydrate (data)
)
.as-console-wrapper {min-height: 100% !important; top: 0}

Este código é inspirado no Ramda (do qual sou autor). O utilitário funciona path, assoce assocPathpossui APIs semelhantes às do Ramda, mas essas são implementações exclusivas (emprestadas de outra resposta .) Como essas são incorporadas ao Ramda, somente a hydratefunção seria necessária se o seu projeto usasse o Ramda.

A grande diferença entre essa e a resposta de Nenad (excelente!) É que a hidratação de nosso objeto leva em consideração a diferença entre as chaves de string, que são consideradas objetos simples, e as numéricas, que são consideradas matrizes. No entanto, como eles são divididos em nossa string inicial ( p_1_category), isso pode levar à ambiguidade, se às vezes você desejar que esses sejam objetos.

Também uso um truque que é um pouco feio e talvez não seja dimensionado para outros valores numéricos: subtraio 1 do número para que o 1in seja p_1_categorymapeado para o índice zeroth. Se seus dados de entrada parecessem, em p_0_category ... p_1_categoryvez de p_1_category ... p_2_categorypodermos pular isso.

Em qualquer caso, há uma chance real de que isso seja contrário aos seus requisitos subjacentes, mas pode ser útil para outras pessoas.

Scott Sauyet
fonte
1
Adoro ver as diferentes abordagens aqui. Talvez pathpoderia ser o ps.reduce((o = {}, p) => o[p], obj)que é justo ps.reduce(prop, obj)?
Obrigado
Desculpe, fornecemos as informações incorretas. O correto deve ser baseado no meu snippet. a propósito, isso é bem legal. pode ser útil no futuro. Obrigado.
Devb 9/04
1
@ Obrigado: Embora essa versão pathpossa funcionar nesse caso específico, ela perde a segurança nula da versão acima. Concordou com a variedade de respostas; parece que perguntas interessantes podem trazer muitas alternativas interessantes.
Scott Sauyet
2

nenhuma classificação necessária

A saída proposta em sua postagem não segue um padrão. Alguns itens agrupam em matrizes, enquanto outros agrupam em objetos. Como objetos semelhantes a matrizes se comportam como matrizes , apenas usaremos objetos.

A saída nesta resposta é a mesma que a de Nenad, mas este programa não exige que as chaves do objeto sejam classificadas previamente -

const nest = (keys = [], value) =>
  keys.reduceRight((r, k) => ({ [k]: r }), value)

const result =
  Object
    .entries(data)
    .map(([ k, v ]) => nest(k.split("_"),  v))
    .reduce(merge, {})

console.log(result)

Resultado -

{
  m: {
    name: "my name",
    address: "my address"
  },
  p: {
    1: {
      category: "cat 1",
      name: "name 1"
    },
    2: {
      category: "cat 2",
      name: "name 2",
      contact: "1234567"
    }
  },
  k: {
    id: 111,
    name: "abc"
  }
}

fundir

Estou emprestando um genérico mergeque escrevi em outra resposta. As vantagens de reutilizar funções genéricas são numerosas e não as reiterarei aqui. Leia a postagem original se quiser saber mais -

const isObject = x =>
  Object (x) === x

const mut = (o = {}, [ k, v ]) =>
  (o[k] = v, o)

const merge = (left = {}, right = {}) =>
  Object.entries (right)
    .map
      ( ([ k, v ]) =>
          isObject(v) && isObject(left[k])
            ? [ k, merge (left[k], v) ]
            : [ k, v ]
      )
    .reduce(mut, left)

Mesclagem superficial funciona como esperado -

const x =
  [ 1, 2, 3, 4, 5 ]

const y =
  [  ,  ,  ,  ,  , 6 ]

const z =
  [ 0, 0, 0 ]

console.log(merge(x, y))
// [ 1, 2, 3, 4, 5, 6 ]

console.log(merge(y, z))
// [ 0, 0, 0, <2 empty items>, 6 ]

console.log(merge(x, z))
// [ 0, 0, 0, 4, 5, 6 ]

E fundas profundas também -

const x =
  { a: [ { b: 1 }, { c: 1 } ] }

const y =
  { a: [ { d: 2 }, { c: 2 }, { e: 2 } ] }

console.log(merge (x, y))
// { a: [ { b: 1, d: 2 }, { c: 2 }, { e: 2 } ] }

Expanda o snippet abaixo para ver nosso resultado em seu próprio navegador -

Obrigado
fonte
0

Use o forEachloop no objeto.
Dividir a chave com base no separador e percorrer a matriz
Até a última chave, crie um objeto vazio e mantenha o objeto atual no ponteiro / corredor.
Na última chave, basta adicionar o valor.

const unflatten = (data, sep = "_") => {
  const result = {};
  Object.entries(data).forEach(([keys_str, value]) => {
    let runner = result;
    keys_str.split(sep).forEach((key, i, arr) => {
      if (i === arr.length - 1) {
        runner[key] = value;
      } else if (!runner[key]) {
        runner[key] = {};
      }
      runner = runner[key];
    });
  });
  return result;
};

const data ={
  m_name: 'my name',
  m_address: 'my address',
  p_1_category: 'cat 1',
  p_1_name: 'name 1',
  p_2_category: 'cat 2',
  p_2_name: 'name 2',
  p_2_contact: '1234567',
  k_id: 111,
  k_name: 'abc'
}

console.log(unflatten(data));

sivako
fonte