質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.38%
Lodash

Lodashは、JavaScriptのユーティリティライブラリ。Underscoreの派生ライブラリで、配列・オブジェクトの操作に便利です。また、コードの可読性も高めることができます。

Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

2回答

16095閲覧

ネストされたオブジェクト間の差分を取得する方法

退会済みユーザー

退会済みユーザー

総合スコア0

Lodash

Lodashは、JavaScriptのユーティリティライブラリ。Underscoreの派生ライブラリで、配列・オブジェクトの操作に便利です。また、コードの可読性も高めることができます。

Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

0グッド

3クリップ

投稿2018/10/06 17:01

JSでネストされたオブジェクト間の差分を取得したい場合、どのように実装するのがベターなのでしょうか?
調べながらなんとかLodashというライブラリを使ってほしい結果を取得できましたが、中身がブラックボックなので、なんか良くわからないけど出来たという感覚です。

有識者の方も、その処理は面倒だからライブラリを使うことが多いとおっしゃるのなら、なるほどとなるのですが、周りに相談できる人もいないためTeratailを頼りにご質問させていただきました。

背景

こちら側の商品情報を外部サービスと同期(API連携する)することになりました
こちら側で商品情報を更新した場合、更新APIのパラメータに更新されたオブジェクトをそのまま渡せれば良いのですが、更新されたプロパティのみを渡す必要性がでてきました。

目的

更新されたプロパティのみを元の階層のまま抽出することです。

試したこと

Javascript

1const _ = require('lodash'); 2 3// 更新前のオブジェクト 4const objectA = { 5 "id": "1", 6 "description": "製品Aです。", 7 "images": [ 8 "1.jpg", 9 "2.jpg", 10 "3.jpg", 11 ], 12 "metadata": { 13 "product_id": "6735", 14 "options": [ 15 "color", 16 "size", 17 ] 18 }, 19 "name": "製品A", 20 "active": false, 21 "created": 1511420673, 22 "updated": 1528145536, 23} 24 25// 更新後のオブジェクト 26const objectB = { 27 "id": "1", 28 "description": "製品Bになりました。", 29 "images": [ 30 "5.jpg", 31 ], 32 "metadata": { 33 "product_id": "6735", 34 "options": [ 35 "size", 36 ] 37 }, 38 "name": "製品B", 39 "active": true, 40 "created": 1511420673, 41 "updated": 1528145599, 42} 43 44console.log(_.omitBy(objectB, (v, k) => objectA[k] === v))

試した結果

Javascript

1{ 2 "description": "製品Bになりました。", 3 "images": [ 4 "5.jpg", 5 ], 6 "metadata": { 7 "product_id": "6735", 8 "options": [ 9 "size", 10 ] 11 }, 12 "name": "製品B", 13 "active": true, 14 "updated": 1528145599, 15}

補足情報(FW/ツールのバージョンなど)

Node.js

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

こんにちは。

オブジェクトや配列に対してちょっと込み入った操作をするときに Lodash を利用するという選択自体は、間違いではありません。実際、私も同じ課題に直面したら、まずは、Lodash に便利なメソッドはないか?と探すと思います。

ただし、ご質問に挙げられている以下の _.omitBy を使ったコード

javascript

1_.omitBy(objectB, (v, k) => objectA[k] === v);

だと、objectB から更新されたプロパティだけを残して他を除去(omit)したオブジェクトを得るという、期待した動作を満たせないのでは?という懸念があります。
たとえば以下の例

javascript

1const objectA = { 2 id: 1, 3 description: "製品Aです。", 4 images: [ 5 "1.jpg", 6 "2.jpg", 7 "3.jpg", 8 ], 9}; 10 11const objectB = { 12 id: 1, 13 description: "製品Aです。", 14 images: [ 15 "1.jpg", 16 "2.jpg", 17 "3.jpg", 18 ], 19}; 20 21console.log(_.omitBy(objectB, (v, k) => objectA[k] === v));

では、 objectAobjectB とは内容が同じなので、更新のあったプロパティを持つオブジェクトとしては空オブジェクト {} が返ってきて欲しいところですが、上記を実行すると images が等しくないプロパティとして残ることになり、結果として_.omitByの返すオブジェクトは、以下

javascript

1{ 2 images: [ 3 "1.jpg", 4 "2.jpg", 5 "3.jpg", 6 ] 7}

であることが、console.logの出力で確認できます。
これの検証用のサンプルを以下に上げましたので、ご確認ください。

上記を勘案しまして、オブジェクトの差分を得るのにLodashを使ってみたこと、および
_.omitBy(objectB, (v, k) => objectA[k] === v)
というコードを拝読して、お伝えしたいこととしては、以下の3点です。

  1. Lodashを使った修正案の見つけ方
  2. Lodashを使わずに最小限の要求を満たすものを書いてみる
  3. 基礎を見直すことと応用的なものを使うことのバランスについて

これらについて以下順を追って回答します。

1. Lodashを使った修正案の見つけ方

ご質問に

調べながらなんとかLodashというライブラリを使って

とありました。 また、

周りに相談できる人もいない

ともありました。そのような状況の中で Lodashを見つけ出して、それを使ってみようと思い、実際コードを書いてみたというチャレンジはとてもよいと思います。ですので、まずは Lodashを使って、主題の「ネストされたオブジェクト間の差分を取得する方法」を探ってみましょう。

ご質問から業務でコードを書いている中での課題と思われますが、仕事の中でこの種の問題に遭遇した場合、すでに(優れたプログラマーである)誰かが作ってくれた、より確からしい実装に早くたどりつく必要がありますね。そういうときに、あくまで私の場合は、という限られた話にはなりますが、まずは適切な**(日本語を含まない)英文で**ググってからの stackoverflow または、使うモジュールの発信元であるGitHubレポジトリのissueに拠り所を求めることが多いです。

このご質問の解決策を見つけるときに、Lodashを使うというのは筋として悪くないので、

(1) まずは "lodash difference between two objects" で[グーグル検索](https://www.google.co.jp/search?q=lodash+ difference +between+two+objects) し、

(2) 検索結果で上位に出てきたものをざっと読み、

(3) stackoverflowの投稿が出てきたときは、いくつかの回答の中でどれが使えそうか当たりをつけます。

(4) 今回のご質問の場合、Lo-Dash Essentials という著書も書いている、 Adam Boduch さんによる回答:

を拝借するのがよいと思えました。
(ただし検索結果として出てきた、どの投稿あるいは回答が一番使えるか?を選ぶのは、個人の判断に委ねられるところなので、別の投稿や回答のほうが使えると判断される方もいらっしゃることでしょう。)

上記を経て作成したものが以下です。

javascript

1// 更新前のオブジェクト 2const objectA = { 3 id: 1, 4 description: "製品Aです。", 5 images: [ 6 "1.jpg", 7 "2.jpg", 8 "3.jpg", 9 ], 10 metadata: { 11 product_id: 6735, 12 options: [ 13 "color", 14 "size", 15 ] 16 }, 17 name: "製品A", 18 active: false, 19 created: 1511420673, 20 updated: 1528145536, 21}; 22 23// 更新後のオブジェクト 24const objectB = { 25 id: 1, 26 description: "製品Bになりました。", 27 images: [ 28 "5.jpg", 29 ], 30 metadata: { 31 product_id: 6735, 32 options: [ 33 "size", 34 ] 35 }, 36 name: "製品B", 37 active: true, 38 created: 1511420673, 39 updated: 1528145599, 40}; 41 42// 更新されたプロパティの配列を得る。(https://stackoverflow.com/a/31686152) 43const diffProps = _.reduce(objectA, function(result, value, key) { 44 return _.isEqual(value, objectB[key]) ? 45 result : result.concat(key); 46}, []); 47 48console.log(diffProps); 49 50// 値が更新されたプロパティと、それらのobjectBでの値を持つオブジェクトを作成 51const diffObj = diffProps.reduce((obj, prop) => { 52 obj[prop] = objectB[prop]; 53 return obj; 54}, {}); 55 56console.log(diffObj); 57 58 59

上記のコードを以下に上げましたのでお試しください。

この差分抽出コードだと、objectAとobjectBの内容が同じときは、差分のオブジェクトは以下

のように、期待どおり空オブジェクトになります。

先ほど、
「業務上でこの種の問題に遭遇した場合、すでに(優れたプログラマーである)誰かが作ってくれた、より確からしい実装に早くたどりつく必要がありますね。」
と書きました。その観点でいうと、(1)から(4)のステップで大事なのは、 まずは(1)でいかに適切な英語で検索できるかだと思ってます。ですので、なるべく、stackoverflow のタイトルや、使いたいモジュールの発信元である GitHubレポジトリの issueタイトルにヒットしそうな検索ワードをあれこれと試します。

2. Lodashを使わずに最小限の要求を満たすものを書いてみる

二点目としまして、あらためてterataillerさんのご質問を再読して(老婆心ながら)思うところとしては、ご質問にあるコード

javascript

1_.omitBy(objectB, (v, k) => objectA[k] === v)

によって、差分のあるプロパティだけを取り出せると考えたのでしたら、 厳密な比較演算子=== についての理解を点検する必要があるのでは? ということです。ですので、

中身がブラックボックスなので、なんか良くわからないけど出来たという感覚

のモヤモヤの原因の一端は、=== が true になる条件の理解不足にあるかもしれません。モヤモヤにも良いモヤモヤと悪いモヤモヤとがあって、 ===の理解不足によるモヤモヤは悪いモヤモヤなので、すぐに解消すべきと思います。

言い換えると、

  • Lodash (なり、他の便利な何か)を採用することにし、それを使うために必要な情報に効率よくリーチして、ときには他の優れたプログラマーの成果物を土台として使ったりもしながら、どんなオブジェクトにも使える「オブジェクト一般の差分取得」というような、(いわば)大きなテーマのコードを持ってきて、締め切りに追われがちな限られた開発時間の中で、必要あれば修正も加えて、自分の目の前にある課題解決に使うことができる。

  というのは応用編のスキル、しかもプログラミングの技術力は半分であとの半分は仕事をさばく器用さに近いものですが、それよりも

  • === で true になる条件という基礎知識を正しく活用して、自分の開発業務の範囲内にある、ある特定の形式のオブジェクトを比較するコードを、Lodashのような便利グッズを使わず、スクラッチから難なく書ける。

  というスキルのほうが、獲得すべき順序として優先度が(かなり)高い

ということです。

そこで、以下に挙げたいくつかのページ

で説明している基礎知識を確認するための実践として、オブジェクト一般について比較するコードではなく、ご質問で検討の対象としている、以下の形式

javascript

1{ 2 id: 整数, 3 description: 文字列, 4 images: 文字列を要素とする配列, 5 metadata: { 6 product_id: 整数, 7 options: 文字列を要素とする配列 8 }, 9 name: 文字列, 10 active: ブール値, 11 created: 整数(タイムスタンプ), 12 updated: 整数(タイムスタンプ), 13}

であることは分かっている2つのオブジェクト objectA と objectB とを比較して差分のプロパティだけを持つオブジェクトを返す関数なりメソッドなりを書いてみるのはいかがでしょうか? これをスクラッチから、Lodashのような便利なものを使わずに書いてみるとよいかもしれません。それがterataillerさんにとっての(次の一歩となる、)

ベターな

コードになると思いますし、それが書けてからLodashを使った汎用的なコードを採用することにしても遅くはありません。
また、ご質問にある、

中身がブラックボックスなので、なんか良くわからないけど出来たという感覚

と書かれている、誰かが作った中身の分からないものに乗ってしまっていることでのモヤモヤ(これは良いほうのモヤモヤです)を感じることのできるセンスを維持することも大事ですので、そのための練習問題として、(上記の、ある特定の形式のオブジェクトのプロパティ比較と差分オブジェクトの作成が書けた後に、) 一般的なオブジェクトの比較という大きいテーマに、Lodash を使わないで取り組むのもよい修練になると思います。それに取り組むと、おそらく

についている回答のようなコードを書くことになり、JavaScriptの基礎確認に役立つと思います。

3. 基礎を見直すことと応用的なものを使うことのバランスについて

ここは意見の分かれるところだと思いますが、自分の考えを書いておきます。

私は「基礎がしっかり身についてないうちに、誰かが作ってくれた便利なものに頼ろうとしてはいけません。」というお説教を言うつもりは全くありません。むしろ逆で、基礎があやふやなうちから Lodash (に限らず JQuery でも、React でもいいのですが。) のような、言わば巷で流行の道具に目が行って、それに飛びつくことができるというのは、(これからもキープすべき)良質なセンスだと思います。それで飛びついてはみたものの、それを使ってみようとしたらうまくいかなくて、どうしてうまくいかないのかと試行錯誤した結果、「基礎力が足りない」と自分でちゃんと納得して結論することができ、その対策を(通常の業務時間とは別の時間を使うことになるかもしれませんが。)さっさと始めることが大事です。そのようにして、何か応用的なことをやろうとして、理解不足を感じた基礎項目に立ち返り、基礎を点検できたらまた応用に取り組むということを繰り返すうちに、基礎を見直すことと応用的な取り組みとの間の、自分なりのバランスが出来ていきます。

  
以上、参考になれば幸いです。

投稿2018/10/07 00:05

編集2018/10/10 02:59
jun68ykt

総合スコア9058

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2018/10/09 19:18

前回に引き続き、大変ご丁寧な回答をありがとうございます。 具体例を交え問題解決のプロセスまで、期待以上のご回答をいただけて嬉しい限りです。 何度も読み返して次への糧とさせていただきます!
guest

0

ベターな書き方

JSでネストされたオブジェクト間の差分を取得したい場合、どのように実装するのがベターなのでしょうか?

あなたが最も理解している書き方がベストです。

  • var を使うべきではない。let, const を使うべき。
  • for 文を使うべきではない。forEach, for-of を使うべき。
  • XMLHttpRequest を使うべきではない。Fetchを使うべき。

この手の「推奨する書き方」のようなものが主張される事は確かにありますが、極論をいえば、どれも同じです。
たとえ話ですが、どこかで主張された「推奨する書き方」を良くわからないままに使って、バグを発生させるのでは本末転倒です。
バグを作りこまない為に、「あなたが最も理解している for 文を使う」のはありだと私は思います。
他の機能についても並行して学習し、「あなたが最も理解している for 文」と同等の理解度になった時に、ケース別に機能の取捨選択をすればいいでしょう。
そこまで理解すれば、自分の中に「どれを選ぶべきか」の判断基準を持っているはずです。

初心者にありがちなのは「推奨する機能を教えて下さい。それを覚えます。」というものですが、最終的には全てを覚えなければなりません
それなら、いろいろ試して貰って、その人が一番理解しやすい機能から覚えて貰った方が効率が良いでしょう。

「わからない」の具体化

調べながらなんとかLodashというライブラリを使ってほしい結果を取得できましたが、中身がブラックボックなので、なんか良くわからないけど出来たという感覚です。

これはライブラリに「期待する動作」をよく考えないままに「適当に試したらたまたま出来た」のが原因です。
ライブラリの良いところでも悪いところでもありますが、ブラックボックスのまま使うのは正常動作しているように見えても、意図しないバグが潜んでいる可能性がある為、お勧めしません。
理想的には、そのライブラリのコードを解読し、動作を理解する事です。
次点で公式ドキュメントを読みながら、テストコードを書いて、動作を理解できるまでトライアンドエラーを繰り返す事。
それが出来ない内は、ライブラリを使わず、素のJavaScriptでコードを書く方が習得速度が速いと私は思います。

コードを作る前にアルゴリズムを考えるのが重要です。
はっきりいえば、アルゴリズムが出来ていないうちはコードなんて書いても無駄です。

  • 頭の中で動作を考えながら、頭の中で動かしてみる
  • 紙に書きだしてみる
  • フローチャートを書いてみる
  • consoleデバッグ、BreakPointで書きだしたフロー図と答え合わせ

こうした作業を実行していますか。
もし、やっているなら、質問文にそれを書きましょう。
現在の「なんかよくわからない」から「~のように考え、~をやってみたが、~のようになってしまった」のように、「分からない」を具体化して下さい。

現在の私の状況は「あなたがどこまで理解して、どこが理解できないのか分からない」ので、「何を教えていいかわからない」です。
(こういう場合、「何が分からないのか分からないので、とりあえず、要件を達成したコードを出しておくか」となりがちで、それが狙いなのかもしれませんが、そこは私の暇つぶしに付き合ってくれた対価として提供しましょう。)

コード

アルゴリズムとしては、teratailler さんの過去質問に近いものがあります。
(あえていえば、再帰処理を理解している必要があります)

JavaScript

1'use strcit'; 2function deepCopy (object) { 3 return Object(object) === object && JSON.parse(JSON.stringify(object)) || object; 4} 5 6function diffObject (beforeObject, afterObject) { 7 const add = Object.create(null), remove = Object.create(null), hasOwnProperty = Object.prototype.hasOwnProperty; 8 9 for (let key of new Set(Object.keys(beforeObject).concat(Object.keys(afterObject)))) { 10 const beforeValue = beforeObject[key], afterValue = afterObject[key]; 11 12 if (!hasOwnProperty.call(beforeObject, key)) { 13 add[key] = deepCopy(afterValue); 14 } else if (!hasOwnProperty.call(afterObject, key)) { 15 remove[key] = deepCopy(beforeValue); 16 } else if (beforeValue !== afterValue) { 17 if (Object(beforeValue) === beforeValue && Object(afterValue) === afterValue) { 18 const child = diffObject(beforeValue, afterValue); 19 20 remove[key] = deepCopy(child.beforeValue); 21 add[key] = deepCopy(child.afterValue); 22 } else { 23 remove[key] = deepCopy(beforeValue); 24 add[key] = deepCopy(afterValue); 25 } 26 } 27 } 28 29 return {add: add, remove: remove}; 30}

Re: teratailler さん

投稿2018/10/07 05:00

編集2018/10/07 05:19
think49

総合スコア18189

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2018/10/09 19:18

前回に引き続き、大変ご丁寧な回答をありがとうございます。 今回はライブラリに頼ってしまいましたが、このような問題にも素のJavaScriptで書けるように精進します。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.38%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問