アンダースコア(_)変数名を含むデータセットがあります。以下のような:
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);
並べ替えは必要ありません
投稿で提案された出力は、パターンに従っていません。一部の項目は配列にグループ化され、他の項目はオブジェクトにグループ化されます。配列のようなオブジェクトはlike arrayのように動作するため、オブジェクトのみを使用します。
この回答の出力は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 } ] }
以下のスニペットを展開して、ご使用のブラウザで結果を確認してください-
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 nest = (keys = [], value) =>
keys.reduceRight((r, k) => ({ [k]: r }), value)
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'}
const result =
Object
.entries(data)
.map(([ k, v ]) => nest(k.split("_"), v))
.reduce(merge, {})
console.log(JSON.stringify(result, null, 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
- indexedオブジェクトではなく、オブジェクトのプレーン配列になりました。これが役に立たない可能性は十分ありますが、興味深い代替手段です。また、提供したサンプル出力とは2番目の違いがあります。元のコードとNenadからの回答の両方が、リクエストされたm: {name: "my name", address: "my address"}
ではなくm: [{name: "my name"}, {address: "my address"}]
を返します。これは私にははるかに論理的であるように思われます。
これを行うコードは次のとおりです。
// 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 (私は著者です)に触発されています。ユーティリティ関数path
、assoc
、およびassocPath
は、Ramdaと同様のAPIを持っていますが、これらは固有の実装です( another answer から借用)。これらはRamdaに組み込まれています。プロジェクトでRamdaを使用している場合は、hydrate
関数のみが必要です。
これとNenadの(優れた!)回答の大きな違いは、オブジェクトのハイドレーションでは、プレーンオブジェクトであると想定される文字列キーと、配列であると想定される数値キーの違いが考慮されるということです。ただし、これらは最初の文字列(p_1_category
)から分割されているため、場合によってはwantにする必要がある場合にあいまいになる可能性がありますオブジェクト。
また、少し見苦しく、他の数値にスケーリングできないトリックを使用しています。1
のp_1_category
が0番目のインデックスにマップされるように、数値から1を引きます。入力データがp_0_category ... p_1_category
ではなくp_1_category ... p_2_category
のように見える場合は、これをスキップできます。
いずれにせよ、これが根本的な要件に反する可能性は十分にありますが、他の人にとっては役立つかもしれません。
オブジェクトで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));