オブジェクトをネストされたオブジェクトに分割する方法は?(再帰的な方法)


8

アンダースコア(_)変数名を含むデータセットがあります。以下のような:

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

それらをネストされたオブジェクト/配列に分割したいのですが、以下が結果です。

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

ハードコードする代わりに、それを達成するための再帰的なメソッドをどのように書くことができますか?多分、 "p_2_one_two_category: 'value'"などのより深くネストされたオブジェクトを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);


コードとNenadからの回答のコードの両方が、サンプル出力とは少し異なるものを生成します。(mは、単一プロパティオブジェクトの配列ではなく、プレーンオブジェクトです。)これらの結果は、サンプル出力よりもはるかに論理的に見えます。それはあなたが実際に望んでいることですか?
Scott Sauyet

@ScottSauyet ops、私は間違った結果を入れました、それを更新しましたが、私のスニペットは彼がしたことと同じです。はい、それが私が欲しいものです..ありがとう
Devb

回答:


6

reduceオブジェクトだけで同様のネストされた構造を作成するメソッドを使用できます。

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)


2

その出力形式が本当に探していたものなのか、それとも単に達成できる最高のものなのかはわかりません。1つの代替案は、次のようなものを生成することです。

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

これとコードの出力との間には1つの大きな違いがあります。 -および- インデックス付きオブジェクトpではなく、オブジェクトのプレーン配列に1なり2ました。これが役に立たない可能性は十分ありますが、興味深い代替手段です。また、提供したサンプル出力とは2番目の違いがあります。m: {name: "my name", address: "my address"}リクエストされm: [{name: "my name"}, {address: "my address"}]たものではなく、元のコードとNenadからの回答の両方が返されます。これは私にははるかに論理的であるように思われ、私もこの方法でそれを行いました。

これを行うコードは次のとおりです。

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

このコードはRamda(筆者は筆者)に触発されたものです。ユーティリティ機能pathassocおよびassocPathRAMDAのと同様のAPIを持っているが、これらは(から借りユニーク実装されている別の答え。)これらはRAMDAに組み込まれているので、唯一のhydrateプロジェクトがRAMDAを使用した場合の機能は必要であろう。

これとNenadの(優れた!)回答の大きな違いは、オブジェクトのハイドレーションでは、プレーンオブジェクトと想定される文字列キーと配列と想定される数値キーの違いが考慮されることです。ただし、これらは最初の文字列(p_1_category)から分離されているため、オブジェクトにしたい場合はあいまいになる可能性があります。

また、少し見苦しく、他の数値にスケーリングできないトリックを使用します。数値から1を引いて、1in p_1_categoryが0番目のインデックスにマップされるようにします。入力データが次のようp_0_category ... p_1_categoryに見える場合は、p_1_category ... p_2_categoryこれをスキップできます。

いずれにせよ、これが根本的な要件に反する可能性は十分にありますが、他の人にとっては役立つかもしれません。


1
ここでさまざまなアプローチを見るのが大好きです。たぶん、path可能性ps.reduce((o = {}, p) => o[p], obj)だけありますかps.reduce(prop, obj)
ありがとう

申し訳ありませんが、間違った情報を提供しました。正しいものは私のスニペットに基づいているはずです。ところで、それはかなりクールです。将来的には役立つかもしれません。ありがとう。
Devb

1
@Thankyou:このバージョンのpathはこの特定のケースで機能する可能性がありますが、上記のバージョンのnull安全性が失われます。答えの多様性について合意した。興味深い質問は多くの興味深い選択肢を引き出すことができるようです。
Scott Sauyet

2

並べ替えは不要

投稿で提案された出力は、パターンに従っていません。一部の項目は配列にグループ化され、他の項目はオブジェクトにグループ化されます。オブジェクトの振る舞いのアレイのようなので配列のように、我々はただのオブジェクトを使用します。

この回答の出力はNenadの出力と同じですが、このプログラムではオブジェクトのキーを事前にソートする必要はありません-

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)

出力-

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

マージ

merge別の答えで書いたジェネリックを借りています。ジェネリック関数を再利用する利点は数多くあります。ここではそれらを繰り返し説明しません。詳細については、元の投稿をご覧ください-

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)

浅いマージは期待どおりに機能します-

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 ]

そして深いマージも-

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

以下のスニペットを展開して、ブラウザで結果を確認してください-


0

forEachオブジェクトのループを使用します。
セパレーターに基づいてキーを分割し、配列をトラバースし
ます。最後のキーまで、空のオブジェクトを作成し、ポインター/ランナーで現在のオブジェクトを維持します。
最後のキーで、値を追加するだけです。

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.