¿Cómo dividir un objeto en un objeto anidado? (Manera recursiva)

8

Tengo un conjunto de datos que contiene el nombre de la variable de subrayado (_). Como a continuación:

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

Quiero dividirlos en un objeto / matriz anidado, a continuación se muestra el resultado que quiero.

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

¿Cómo puedo escribir un método recursivo para lograrlo en lugar de codificarlo? Tal vez debería permitir manejar objetos anidados más profundos como "p_2_one_two_category: 'value'" en 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
fuente
Tanto su código como el código en la respuesta de Nenad generan algo ligeramente diferente de su salida de muestra. ( mes un objeto simple, no una matriz de objetos de propiedad única). Estos resultados parecen mucho más lógicos que su salida de muestra. ¿Es eso lo que realmente quieres?
Scott Sauyet
@ScottSauyet ops, puse el resultado incorrecto, lo actualicé, pero mi fragmento es el mismo que él. sí, eso es lo que quiero ... Gracias
Devb

Respuestas:

6

Podría usar un reducemétodo que creará una estructura anidada similar con solo 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
fuente
2

No sé si ese formato de salida era lo que realmente estaba buscando o simplemente lo mejor que pudo lograr. Una alternativa sería generar algo como esto:

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

Hay una gran diferencia entre esto y la salida de su código. pes ahora un array plano de objetos en lugar de un 1- y 2objeto -indexed. Es muy posible que esto no sea útil para usted, pero es una alternativa interesante. También hay una segunda diferencia con respecto a la salida de muestra que proporcionó. Tanto su código original como la respuesta de Nenad devuelven en m: {name: "my name", address: "my address"}lugar de la solicitada m: [{name: "my name"}, {address: "my address"}]. Esto me parece mucho más lógico, y también lo he hecho de esta manera.

Aquí hay un código que haría esto:

// 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 está inspirado en Ramda (del cual soy autor). Las funciones de utilidad path, assocy assocPathtienen APIs similares a Ramda de, pero estos son implementaciones únicas (tomados de otra respuesta .) Puesto que éstos se construyen en Ramda, sólo el hydratesería necesario función de si su proyecto utiliza Ramda.

La gran diferencia entre esto y la respuesta de Nenad (¡excelente!) Es que nuestra hidratación de objetos tiene en cuenta la diferencia entre las teclas de cadena, que se supone que son para objetos simples, y las numéricas, que se supone que son para matrices. Sin embargo, dado que estos están separados de nuestra cadena inicial ( p_1_category), esto podría generar ambigüedad si a veces desea que sean objetos.

También utilizo un truco que es un poco feo y que tal vez no se escalará a otros valores numéricos: resto 1 del número para que el 1in se p_1_categoryasigne al índice cero. Si los datos de entrada parecía que p_0_category ... p_1_categoryen lugar de p_1_category ... p_2_categoryque podría saltar este.

En cualquier caso, existe una posibilidad real de que esto sea contrario a sus requisitos subyacentes, pero podría ser útil para otros.

Scott Sauyet
fuente
1
Me encanta ver los diferentes enfoques aquí. Tal vez pathpodría ser lo ps.reduce((o = {}, p) => o[p], obj)que es justo ps.reduce(prop, obj)?
Gracias
Lo siento, he proporcionado la información incorrecta. El correcto debe basarse en mi fragmento. por cierto, eso es genial. Puede ser útil en el futuro. Gracias.
Devb
1
@Gracias: Si bien esa versión de pathpodría funcionar para este caso específico, pierde la seguridad nula de la versión anterior. De acuerdo sobre la variedad de respuestas; Parece que las preguntas interesantes pueden traer muchas alternativas interesantes.
Scott Sauyet
2

no se requiere clasificación

El resultado propuesto en su publicación no sigue un patrón. Algunos elementos se agrupan en matrices, mientras que otros se agrupan en objetos. Dado que los objetos tipo matriz se comportan como matrices , solo utilizaremos objetos.

El resultado en esta respuesta es el mismo que el de Nenad, pero este programa no requiere que las claves del objeto estén ordenadas de antemano:

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)

Salida -

{
  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"
  }
}

unir

Estoy tomando prestado un genérico mergeque escribí en otra respuesta. Las ventajas de reutilizar funciones genéricas son numerosas y no las reiteraré aquí. Lea la publicación original si desea saber más:

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)

Shallow combina el trabajo como se esperaba

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 ]

Y las fusiones profundas también ...

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 el fragmento a continuación para ver nuestro resultado en su propio navegador:

Gracias
fuente
0

Usa el forEachbucle en el objeto.
Divida la clave según el separador y recorra la matriz
Hasta la última clave, cree un objeto vacío y mantenga el objeto actual en puntero / corredor.
En la última clave, simplemente agregue el 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
fuente