JavaScriptオブジェクトのプロパティのサブセットを取得する方法


425

オブジェクトがあるとしましょう:

elmo = { 
  color: 'red',
  annoying: true,
  height: 'unknown',
  meta: { one: '1', two: '2'}
};

プロパティのサブセットを持つ新しいオブジェクトを作成したい。

 // pseudo code
 subset = elmo.slice('color', 'height')

 //=> { color: 'red', height: 'unknown' }

どうすればこれを達成できますか?


2
Underscoreライブラリーには、次のようなヘルパー関数がたくさんありますので、チェックしてください:underscorejs.org/#pick
Stefan

4
emo.slice一見言ったと思いました。
ビルクリスウェル

1
考え直して...サブセットを作成しません...
Andrew

回答:


675

オブジェクトの破壊とプロパティの省略表現の使用

const object = { a: 5, b: 6, c: 7  };
const picked = (({ a, c }) => ({ a, c }))(object);

console.log(picked); // { a: 5, c: 7 }


フィリップ・ケウィッシュから:

これは実際には、即座に呼び出される無名関数です。これはすべて、MDNのDestructuring Assignmentページにあります。これが展開されたフォームです

let unwrap = ({a, c}) => ({a, c});

let unwrap2 = function({a, c}) { return { a, c }; };

let picked = unwrap({ a: 5, b: 6, c: 7 });

let picked2 = unwrap2({a: 5, b: 6, c: 7})

console.log(picked)
console.log(picked2)


37
これを行う方法についてどのように学びましたか?私が見たドキュメントや記事(MDNを含む)のどこにも、オブジェクトの構造化で使用されている矢印の構文は示されていません。これは非常に知っておくと便利です。
papiro 2017年

52
これは実際には、即座に呼び出される無名関数です。これはすべて、MDNのDestructuring Assignmentページにあります。ここで展開された形式がある: let unwrap = ({a, c}) => ({a, c}); let unwrap2 = function({a, c}) { return { a, c }; }; let picked = unwrap({ a: 5, b: 6, c: 7 });
フィリップKewisch

8
スプレッド演算子で動的にそれを行う方法はありますか?
Tom Sarduy 2017年

4
@TomSarduyは、削除する小道具を指定したい場合、たとえばとしてconst { b, ...picked } = object作成する場合に、restを使用できます。単にbを削除するように指定しました。ただし、使用していないvarを宣言すると、eslintはおそらくあなたを困らせるでしょう。picked{ a: 5, c: 7 }
カリブーのジョシュ2017

24
ここでの欠点は、一連の属性名を2回完全に入力する必要があることです。多くの属性を選択する必要がある場合、これはかなりの問題になる可能性があります。
Gershom 2018年

144

Lodashを見てみることをお勧めします。それは多くの素晴らしいユーティリティ機能を持っています。

たとえば、pick()まさにあなたが求めるものです:

var subset = _.pick(elmo, ['color', 'height']);

バイオリン


2
underscore.jsにも同じ
Dan

1
選択せずに特定のフィールドのみを除外する機能はありますか?私のjsonには約50のフィールドがあり、2つのフィールド以外のすべてが必要です。
Shrikant Prabhu

7
うん!_.omit(elmo, ['voice'])すべてを返すために使用できますがvoice
xavdid

このアプローチについて私が気に入らないのは、フィールド名を引用符で囲んでいるため、タイプミスの影響を受けやすく、IDEでプロパティの名前を変更するなどの一般的なリファクタリングでは認識されないなどです。
アンディ

117

ES6を使用している場合、デストラチャを使用してこれを行う非常に簡潔な方法があります。分解により、スプレッドを使用してオブジェクトに簡単に追加できますが、同じ方法でサブセットオブジェクトを作成することもできます。

const object = {
  a: 'a',
  b: 'b',
  c: 'c',
  d: 'd',
}

// Remove "c" and "d" fields from original object:
const {c, d, ...partialObject} = object;
const subset = {c, d};

console.log(partialObject) // => { a: 'a', b: 'b'}
console.log(subset) // => { c: 'c', d: 'd'};

6
これはフィールドを削除するためだけに機能し、既知のサブセットを選択するためではありませんか?削除する可能性のある不明なフィールドが無限にある可能性がありますが、一部の人々が探している可能性があります
Alexander Mills

確かに、しかしそれは新しいオブジェクトに再割り当てできるいくつかの既知のフィールドを削除できるので、それでもこの質問に関連していると感じます。さらに説明するために回答に追加されました。
ローレン、

1
これは本質的にであるものと同じである@Ivan Nosovの答え、それはここで、より理解しやすい方法で説明していますいえ、
icc97

81

それは少しより冗長ですが、あなたは誰もが使用することにより、2年前のためにアンダースコア/ lodashを推薦されたものを達成することができArray.prototype.reduceを

var subset = ['color', 'height'].reduce(function(o, k) { o[k] = elmo[k]; return o; }, {});

このアプローチは反対側からそれを解決します。オブジェクトを取得してプロパティ名をオブジェクトに渡して抽出するのではなく、プロパティ名の配列を取得してそれらを新しいオブジェクトに縮小します。

最も単純なケースでは詳細ですが、ここでのコールバックはかなり便利です。たとえば、新しいオブジェクトで 'color'プロパティを 'color'に変更したり、配列をフラット化したりするなど、いくつかの一般的な要件を簡単に満たすことができます。あるサービス/ライブラリからオブジェクトを受け取り、別の場所で必要な新しいオブジェクトを構築するときに行う必要があること。アンダースコア/ロダッシュは優れた、適切に実装されたライブラリですが、これはベンダーへの依存を減らすための私の推奨アプローチであり、サブセット構築ロジックがより複雑になった場合は、よりシンプルで一貫性のあるアプローチです。

編集:同じのes7バージョン:

const subset = ['color', 'height'].reduce((a, e) => (a[e] = elmo[e], a), {});

編集:カレーの良い例も!「pick」関数が別の関数を返すようにします。

const pick = (...props) => o => props.reduce((a, e) => ({ ...a, [e]: o[e] }), {});

上記は、その場で「ピッカー」を作成できることを除いて、他の方法にかなり近いです。例えば

pick('color', 'height')(elmo);

このアプローチの特に優れている点は、選択した「小枝」を関数をとるあらゆるものに簡単に渡すことができることです。例Array#map

[elmo, grover, bigBird].map(pick('color', 'height'));
// [
//   { color: 'red', height: 'short' },
//   { color: 'blue', height: 'medium' },
//   { color: 'yellow', height: 'tall' },
// ]

3
es6を使用すると、アロー関数とObject.assignの戻り値を介してこれをよりクリーンにすることができます(オブジェクトプロパティに割り当てるとプロパティ値が返されますが、Object.assignはオブジェクトを返します)
Josh from Qaribou

もう1つのes6注:通常は代入または引数を分解するだけなので、これを行う必要はほとんどありません。たとえば、function showToy({ color, height }) {必要なものだけをスコープに入れます。このreduceアプローチは、シリアル化のためにオブジェクトを簡略化する場合に主に意味があります。
カリブーのジョシュ

6
そのES6バージョンは、反復ごとにすべてのプロパティのコピーを作成するため、パフォーマンスが低下します。O(n)演算をO(n ^ 2)にします。最初のコードブロックに相当するES6はconst pick = (obj, props) => props.reduce((a, e) => (a[e] = obj[e], a), {});
4castle

@ 4castleうん、いいね-それほど反復する意味はありません。私はカンマ構文が好きです-リターンの束よりも優れています。
カリブーのジョシュ2017

1
@ShevchenkoViktor私は実際に元のes6バージョンでそのアプローチを使用しましたが、@ 4castleのコメントの後でそれを変更しました。スプレッドはもっとはっきりしていると思いますが、ボトルネックになりやすいコード内の大きなオブジェクトの場合は大きな違いです(たとえばfetch、から返されるレンダリングデータの遅延)。そのため、コンマ演算子の使用法を説明するコメントを追加することをお勧めします。
Josh、Qaribouから2017

45

ES6の破壊

構文の分解により、関数パラメーターまたは変数のいずれかを使用して、オブジェクトを分解および再結合できます。

制限は、キーのリストが事前に定義されていることであり、質問が言及するように、それらを文字列としてリストすることはできません。キーが英数字以外の場合、構造化はさらに複雑になりますfoo_bar

欠点は、キーのリストを複製する必要があることです。これにより、リストが長い場合に冗長コードが生成されます。この場合、構造化によってオブジェクトリテラル構文が重複するため、リストをそのままコピーして貼り付けることができます。

利点は、ES6にとって自然なパフォーマンスの高いソリューションであることです。

IIFE

let subset = (({ foo, bar }) => ({ foo, bar }))(obj); // dupe ({ foo, bar })

一時変数

let { foo, bar } = obj;
let subset = { foo, bar }; // dupe { foo, bar }

文字列のリスト

選択されたキーの任意のリストは、質問に必要な文字列で構成されます。これにより、事前定義せずに、などのキー名を含む変数を使用できますpick(obj, 'foo', someKey, ...moreKeys)

JSエディションごとにワンライナーが短くなります。

ES5

var subset = Object.keys(obj)
.filter(function (key) { 
  return ['foo', 'bar'].indexOf(key) >= 0;
})
.reduce(function (obj2, key) {
  obj2[key] = obj[key];
  return obj2;
}, {});

ES6

let subset = Object.keys(obj)
.filter(key => ['foo', 'bar'].indexOf(key) >= 0)
.reduce((obj2, key) => Object.assign(obj2, { [key]: obj[key] }), {});

またはコンマ演算子を使用して:

let subset = Object.keys(obj)
.filter(key => ['foo', 'bar'].indexOf(key) >= 0)
.reduce((obj2, key) => (obj2[key] = obj[key], obj2), {});

ES2019

ECMAScript 2017にはがObject.entriesありArray.prototype.includes、ECMAScript 2019にはがありますObject.fromEntries。これらは必要に応じてポリフィルすることができ、タスクを容易にします。

let subset = Object.fromEntries(
  Object.entries(obj)
  .filter(([key]) => ['foo', 'bar'].includes(key))
)

ワンライナーは、Lodashにpick似たヘルパー関数としてomit、またはキーのリストが引数を介して渡されるように書き換えることができます。

let pick = (obj, ...keys) => Object.fromEntries(
  Object.entries(obj)
  .filter(([key]) => keys.includes(key))
);

let subset = pick({ foo: 1, qux: 2 }, 'foo', 'bar'); // { foo: 1 }

不足しているキーに関する注意

構造化と従来のLodashのようなpick関数の主な違いは、構造化にundefinedは、サブセットに値を持つ存在しない選択されたキーが含まれることです。

(({ foo, bar }) => ({ foo, bar }))({ foo: 1 }) // { foo: 1, bar: undefined }

この動作は望ましい場合と望ましくない場合があります。構文を破壊するために変更することはできません。

一方では、pick代わりに選んだキーのリストを繰り返すことによって行方不明のキーを含むように変更することができます:

let inclusivePick = (obj, ...keys) => Object.fromEntries(
  keys.map(key => [key, obj[key]])
);

let subset = inclusivePick({ foo: 1, qux: 2 }, 'foo', 'bar'); // { foo: 1, bar: undefined }

11
この回答が最近のものであり、したがって、それに値する露出を得られていないのは、なんて残念なことでしょう。IMO完全性、シンプルさ、多様性、そしてただの実用性のために受け入れられた答えであるべきです。ES6バージョンは、最も便利なスニペットライブラリに保存します。
VanAlbert

ES5-「Uncaught ReferenceError:obj is not defined」
Nikhil Vartak

@NikhilVartak obj変数はオブジェクトを格納する変数であることが期待されています。変数名が異なる場合は、の代わりに使用してくださいobj
Estus Flask

悪い!見落としましたObject.keys(obj)。眠いです。
Nikhil Vartak

34

コアライブラリに組み込まれているようなものはありませんが、オブジェクトの構造化を使用してそれを行うことができます...

const {color, height} = sourceObject;
const newObject = {color, height};

それを行うユーティリティ関数を書くこともできます...

const cloneAndPluck = function(sourceObject, keys) {
    const newObject = {};
    keys.forEach((obj, key) => { newObject[key] = sourceObject[key]; });
    return newObject;
};

const subset = cloneAndPluck(elmo, ["color", "height"]);

Lodashなどのライブラリにもあり_.pick()ます。


1
素晴らしい、私はforEachを次のように変更する必要がありました:keys.forEach(key => {newObject [key] = sourceObject [key];});。これが理にかなっている場合は、コメントを更新してください。
カンダン

34

使用した回答がないため、この回答を追加しますComma operator

destructuring assignmentand ,演算子を使用すると、非常に簡単です。

const object = { a: 5, b: 6, c: 7  };
const picked = ({a,c} = object, {a,c})

console.log(picked);

動的プロパティ割り当ての特定の拡張により、次のようになります。


2
私の悪い、私はそれが機能しなかったことを前に何をしたかわかりませんが、それは機能しているようです
ekkis

1
それが
解体に関して

1
この解決策は賢いですが、厳密モード(つまり'use strict')では機能しません。私が取得しますReferenceError: a is not defined
キンバウディ

8
このアプローチは、2つの変数と、現在のスコープ汚染することを注意aしてcいないコンテキストに応じてランダムに上書きローカルまたはグローバルVARSに気をつけては- 。(承認された回答では、インライン関数で2つのローカル変数を使用することでこの問題を回避しています。これは、即時実行するとスコープ外になります。)
mindplay.dk

2
名前空間の汚染により、これは完全に非現実的です。オブジェクトのプロパティと一致するスコープ内の変数が既に存在することは非常に一般的です。そのため、prop省略表現+構造化が存在します。元の例のように高さまたは色が既に定義されている可能性が非常に高くなります。
ジョシュ

23

もう1つの解決策:

var subset = {
   color: elmo.color,
   height: elmo.height 
}

これは、これまでのほとんどの答えよりもはるかに読みやすく見えますが、おそらくそれは私だけです!


9
派手で混乱を招くコードよりも生産性を優先します。実際のソフトウェアエンジニアリングでは、これが最も読みやすく保守しやすいソリューションです。
Janos

1
はい、しかし、私にとっては、構造化と省略表記を使用することの1つの利点は、エラーが発生しにくいことです。私が誤ってコードをコピーして貼り付けてしまうたびにペニーsubset = {color: elmo.color, height: elmo.color}があったとしたら、少なくとも...まあ、おそらく10セントだったでしょう。
JHH

私はそれがDRYではないので、破壊的な略記をエラーが発生しにくいとは呼びません
gman

同意する必要があります。不要な変数でコンテキストを汚染することなく、これは断然最も読みやすいソリューションです。残りは非常に混乱しているように見えます。私は2番目に見るコードを理解することを好みます。
アンドリュー

11

Lodashも使用できます。

var subset = _.pick(elmo ,'color', 'height');

補足として、「elmo」の配列があるとします。

elmos = [{ 
      color: 'red',
      annoying: true,
      height: 'unknown',
      meta: { one: '1', two: '2'}
    },{ 
      color: 'blue',
      annoying: true,
      height: 'known',
      meta: { one: '1', two: '2'}
    },{ 
      color: 'yellow',
      annoying: false,
      height: 'unknown',
      meta: { one: '1', two: '2'}
    }
];

lodashを使用して同じ動作が必要な場合は、次のようにします。

var subsets = _.map(elmos, function(elm) { return _.pick(elm, 'color', 'height'); });

10

この質問で説明したように、JavaScriptでは動的に名前が付けられた変数に分解することはできませ

動的に設定したキーは、次のようにオブジェクトを変異させずに機能を減らす使用することができます。

const getSubset = (obj, ...keys) => keys.reduce((a, c) => ({ ...a, [c]: obj[c] }), {});

const elmo = { 
  color: 'red',
  annoying: true,
  height: 'unknown',
  meta: { one: '1', two: '2'}
}

const subset = getSubset(elmo, 'color', 'annoying')
console.log(subset)

ただし、単一のクローンを更新するのではなく、反復ごとに新しいオブジェクトを作成することに注意してください。– mpen

以下は、単一のクローンでreduceを使用するバージョンです(reduceに渡される初期値の更新)。

const getSubset = (obj, ...keys) => keys.reduce((acc, curr) => {
  acc[curr] = obj[curr]
  return acc
}, {})

const elmo = { 
  color: 'red',
  annoying: true,
  height: 'unknown',
  meta: { one: '1', two: '2'}
}

const subset = getSubset(elmo, 'annoying', 'height', 'meta')
console.log(subset)


1
驚くばかり![c]の括弧がいかに重要であるかを理解していなかったので、しばらくの間私を驚かせました。それがどういうわけか、cがプロパティの名前ではなく値として見られる原因となっていると思います。とにかく、とてもクール。+1
ジョンフェアバンクス

相棒、ありがとな!その使用法は、evalを使用せずに汎用関数を有効にするJavaScriptについて私が気に入っている点の1つです。簡単に言うと、実行時にdictのキーを変数に設定できるようにすることです。var key = 'someKey'を定義すると、それを{[key]: 'value'}として使用でき、{someKey: 'value'}が得られます。すごくかっこいい。
Muhammet Enginar

1
ただし、単一のクローンを更新するのではなく、反復ごとに新しいオブジェクトを作成することに注意してください。
mpen

1
@mpen良い発見。キーの配列を渡す代わりに引数を拡散することも提案したので、単一のクローンを変更するバージョンを追加しました。
ムハメットエンジニア

7

TypeScriptソリューション:

export function pick<T extends object, U extends keyof T>(obj: T, paths: Array<U>) {
    return paths.reduce((o, k) => {
        o[k] = obj[k];
        return o;
    }, Object.create(null));
}

入力情報はオートコンプリートも可能にします:

トリックのDefinitelyTypedへのクレジットU extends keyof T


私のために働いていません。
Nuthinking

@Nuthinking VSコードを使用していますか?
mpen

はい、VS Codeを使用しています。
Nuthinking

@Nuthinkingそして、あなたはそれを.tsファイルに入れますか?うまくいくはずです。もう一度試したところです
mpen



3

ちょうど別の方法...

var elmo = { 
  color: 'red',
  annoying: true,
  height: 'unknown',
  meta: { one: '1', two: '2'}
}

var subset = [elmo].map(x => ({
  color: x.color,
  height: x.height
}))[0]

この関数はオブジェクトの配列で使用できます=)


2

どうですか:

function sliceObj(obj) {
  var o = {}
    , keys = [].slice.call(arguments, 1);
  for (var i=0; i<keys.length; i++) {
    if (keys[i] in obj) o[keys[i]] = obj[keys[i]];
  }
  return o;
}

var subset = sliceObj(elmo, 'color', 'height');

プロパティの値がfalse(または偽)の場合、これは失敗します。jsfiddle.net/nfAs8
Alxandr 2013

だからに変更しましたkeys[i] in obj
elclanrs 2013

2

これはChromeコンソールで機能します。これで何か問題がありますか?

var { color, height } = elmo
var subelmo = { color, height }
console.log(subelmo) // {color: "red", height: "unknown"}

4
これは読みやすいですが、色と高さという2つの不要な変数が作成されます。
user424174

コメントを理解しないでください。OPの要件は、これらの2つの要素を持つオブジェクトを作成することでした
MSi

@MSiこれは1つのオブジェクトを作成するだけでなく、2つの変数も作成します。
Adrianによるデザイン

1

動的プロパティを使用した割り当ての分解

このソリューションは、特定の例に適用されるだけでなく、より一般的に適用できます。

const subset2 = (x, y) => ({[x]:a, [y]:b}) => ({[x]:a, [y]:b});

const subset3 = (x, y, z) => ({[x]:a, [y]:b, [z]:c}) => ({[x]:a, [y]:b, [z]:c});

// const subset4...etc.


const o = {a:1, b:2, c:3, d:4, e:5};


const pickBD = subset2("b", "d");
const pickACE = subset3("a", "c", "e");


console.log(
  pickBD(o), // {b:2, d:4}
  pickACE(o) // {a:1, c:3, e:5}
);

subset4より多くのプロパティを考慮に入れるなどを簡単に定義できます。


1

古き良きArray.prototype.reduce

const selectable = {a: null, b: null};
const v = {a: true, b: 'yes', c: 4};

const r = Object.keys(selectable).reduce((a, b) => {
  return (a[b] = v[b]), a;
}, {});

console.log(r);

この回答では、魔法のコンマ演算子も使用しています。https//developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator

本当に凝りたいなら、これはもっとコンパクトです:

const r = Object.keys(selectable).reduce((a, b) => (a[b] = v[b], a), {});

それをすべて再利用可能な関数にまとめます。

const getSelectable = function (selectable, original) {
  return Object.keys(selectable).reduce((a, b) => (a[b] = original[b], a), {})
};

const r = getSelectable(selectable, v);
console.log(r);

1
  1. 引数を配列に変換する

  2. Array.forEach()プロパティを選択するために使用

    Object.prototype.pick = function(...args) {
       var obj = {};
       args.forEach(k => obj[k] = this[k])
       return obj
    }
    var a = {0:"a",1:"b",2:"c"}
    var b = a.pick('1','2')  //output will be {1: "b", 2: "c"}

4
ネイティブ型のプロトタイプを拡張することは、効果はありますが、悪い習慣と見なされています。ライブラリを作成している場合は、これを行わないでください。
エミールベルジェロン

0
function splice()
{
    var ret = new Object();

    for(i = 1; i < arguments.length; i++)
        ret[arguments[i]] = arguments[0][arguments[i]];

    return ret;
}

var answer = splice(elmo, "color", "height");

0

試す

const elmo={color:"red",annoying:!0,height:"unknown",meta:{one:"1",two:"2"}};

const {color, height} = elmo; newObject = ({color, height});

console.log(newObject); //{ color: 'red', height: 'unknown' }

0

私の2セントをIvan Nosovの回答に追加します。

私の場合、オブジェクトから「スライス」するために多くのキーが必要だったので、非常に速くなり、非常に動的なソリューションではなくなりました。

const object = { a: 5, b: 6, c: 7, d: 8, aa: 5, bb: 6, cc: 7, dd: 8, aaa: 5, bbb: 6, ccc: 7, ddd: 8, ab: 5, bc: 6, cd: 7, de: 8  };
const picked = (({ a, aa, aaa, ab, c, cc, ccc, cd }) => ({ a, aa, aaa, ab, c, cc, ccc, cd }))(object);

console.log(picked);

したがって、ここにevalを使用した動的ソリューションがあります。

const slice = (k, o) => eval(`(${k} => ${k})(o)`);


const object    = { a: 5, b: 6, c: 7, d: 8, aa: 5, bb: 6, cc: 7, dd: 8, aaa: 5, bbb: 6, ccc: 7, ddd: 8, ab: 5, bc: 6, cd: 7, de: 8  };
const sliceKeys = '({ a, aa, aaa, ab, c, cc, ccc, cd })';

console.log( slice(sliceKeys, object) );

-2

注:質問された元の質問はJavaScriptでしたが、以下のソリューションでjQueryを実行できます

必要に応じてjqueryを拡張できます。1つのスライスのサンプルコードを次に示します。

jQuery.extend({
  sliceMe: function(obj, str) {
      var returnJsonObj = null;
    $.each( obj, function(name, value){
        alert("name: "+name+", value: "+value);
        if(name==str){
            returnJsonObj = JSON.stringify("{"+name+":"+value+"}");
        }

    });
      return returnJsonObj;
  }
});

var elmo = { 
  color: 'red',
  annoying: true,
  height: 'unknown',
  meta: { one: '1', two: '2'}
};


var temp = $.sliceMe(elmo,"color");
alert(JSON.stringify(temp));

ここに同じのフィドルがあります:http : //jsfiddle.net/w633z/


16
jqueryとは何ですか?
クリスチャンシュレンスカー、2013

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