Wie teile ich ein Objekt in ein verschachteltes Objekt auf? (Rekursiver Weg)

8

Ich habe einen Datensatz, der den Variablennamen des Unterstrichs (_) enthält. Wie unten:

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

Ich möchte sie in verschachtelte Objekte / Arrays aufteilen. Unten ist das gewünschte Ergebnis.

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

Wie kann ich eine rekursive Methode schreiben, um sie zu erreichen, anstatt sie fest zu codieren? Vielleicht sollte es möglich sein, tiefer verschachtelte Objekte wie "p_2_one_two_category: 'value'" in p: {2: {one: {two: category: 'value'}} zu verarbeiten.

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
quelle
Sowohl Ihr Code als auch der Code in der Antwort von Nenad erzeugen etwas, das sich geringfügig von Ihrer Beispielausgabe unterscheidet. ( mIst ein einfaches Objekt, kein Array von Objekten mit einer Eigenschaft.) Diese Ergebnisse scheinen viel logischer zu sein als Ihre Beispielausgabe. Wollen Sie das eigentlich?
Scott Sauyet
@ScottSauyet ops, ich habe das falsche Ergebnis angegeben, ich habe es aktualisiert, aber mein Snippet ist das gleiche wie das, was er getan hat. Ja, das ist was ich will. Danke
Devb

Antworten:

6

Sie können eine reduceMethode verwenden, die eine ähnliche verschachtelte Struktur nur mit Objekten erstellt.

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

Ich weiß nicht, ob dieses Ausgabeformat das war, wonach Sie wirklich gesucht haben, oder einfach das Beste, was Sie erreichen konnten. Eine Alternative wäre, so etwas zu generieren:

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

Es gibt einen großen Unterschied zwischen dieser und der Ausgabe Ihres Codes. pist jetzt ein einfaches Array von Objekten und kein 1- und 2-indexiertes Objekt. Es ist durchaus möglich, dass dies für Sie nicht hilfreich ist, aber es ist eine interessante Alternative. Es gibt auch einen zweiten Unterschied zu der von Ihnen gelieferten Beispielausgabe. Sowohl Ihr Originalcode als auch die Antwort von Nenad werden m: {name: "my name", address: "my address"}anstelle der angeforderten zurückgegebenm: [{name: "my name"}, {address: "my address"}] . Das scheint mir viel logischer zu sein, und ich habe es auch so gemacht.

Hier ist ein Code, der dies tun würde:

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

Dieser Code ist inspiriert von Ramda (von dem ich ein Autor bin). Die Utility - Funktionen path, assocund assocPathähnlichen APIs Ramda die haben, aber diese sind einzigartig Implementierungen (ausgeliehen eine andere Antwort .) Da diese in Ramda gebaut werden, nur das hydratewürde Funktion notwendig sein , wenn Ihr Projekt Ramda verwendet.

Der große Unterschied zwischen dieser und der (ausgezeichneten!) Antwort von Nenad besteht darin, dass unsere Objekthydratation den Unterschied zwischen Zeichenfolgenschlüsseln, die für einfache Objekte angenommen werden, und numerischen Schlüsseln, die für Arrays angenommen werden, berücksichtigt. Da diese jedoch aus unserer anfänglichen Zeichenfolge ( p_1_category) herausgespalten werden, kann dies zu Mehrdeutigkeiten führen, wenn Sie manchmal möchten , dass diese Objekte sind.

Ich benutze auch einen Trick, der ein bisschen hässlich ist und vielleicht nicht auf andere numerische Werte skaliert: Ich subtrahiere 1 von der Zahl, damit das 1in p_1_categorydem nullten Index zugeordnet wird. Wenn Ihre Eingabedaten p_0_category ... p_1_categorystattdessen p_1_category ... p_2_categoryso aussahen, könnten wir dies überspringen.

In jedem Fall besteht die reale Möglichkeit, dass dies Ihren zugrunde liegenden Anforderungen widerspricht, aber es kann für andere nützlich sein.

Scott Sauyet
quelle
1
Ich liebe es, die verschiedenen Ansätze hier zu sehen. Vielleicht pathkönnte ps.reduce((o = {}, p) => o[p], obj)das gerade sein ps.reduce(prop, obj)?
Danke,
Entschuldigung, ich habe die falschen Informationen angegeben. Das richtige sollte auf meinem Snippet basieren. das ist übrigens ziemlich cool. es könnte in Zukunft nützlich sein. Vielen Dank.
Devb
1
@Thankyou: Während diese Version von pathmöglicherweise für diesen speziellen Fall funktioniert, verliert sie die Nullsicherheit der obigen Version. Einverstanden über die Vielfalt der Antworten; Es scheint, als könnten interessante Fragen viele interessante Alternativen hervorbringen.
Scott Sauyet
2

Keine Sortierung erforderlich

Die vorgeschlagene Ausgabe in Ihrem Beitrag folgt keinem Muster. Einige Elemente gruppieren sich zu Arrays, während andere sich zu Objekten gruppieren. Da sich Array-ähnliche Objekte wie Arrays verhalten , verwenden wir nur Objekte.

Die Ausgabe in dieser Antwort ist dieselbe wie die von Nenad, aber dieses Programm erfordert nicht, dass die Schlüssel des Objekts vorher sortiert werden -

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)

Ausgabe -

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

verschmelzen

Ich leihe mir ein Generikum aus merge, das ich in einer anderen Antwort geschrieben habe. Die Wiederverwendung generischer Funktionen bietet zahlreiche Vorteile, die ich hier nicht wiederholen werde. Lesen Sie den Originalbeitrag, wenn Sie mehr wissen möchten -

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)

Flache Zusammenführung der Arbeit wie erwartet -

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 ]

Und auch tiefe Verschmelzungen -

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

Erweitern Sie das folgende Snippet, um unser Ergebnis in Ihrem eigenen Browser anzuzeigen.

Vielen Dank
quelle
0

Verwenden Sie die forEachSchleife für das Objekt.
Teilen Sie den Schlüssel basierend auf dem Trennzeichen und durchlaufen Sie das Array.
Erstellen Sie bis zum letzten Schlüssel ein leeres Objekt und behalten Sie das aktuelle Objekt im Zeiger / Läufer bei.
Fügen Sie beim letzten Schlüssel einfach den Wert hinzu.

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
quelle