二つのJSON、差分を出したかった。
ちょっと自分の趣味&お仕事の関係で、以下のようなJSONを取得する機会がよくあった(もちろん以下のは適当な例)。
[
{"id":"01", "name":"山田", "obj":"old", "data":"重複"},
{"id":"02", "name":"野田", "obj":"old", "data":""},
{"id":"04", "name":"森岡", "obj":"old", "data":"重複"},
{"id":"05", "name":"稲田", "obj":"old", "data":"重複"},
{"id":"09", "name":"杉山", "obj":"old", "data":""},
{"id":"10", "name":"益子", "obj":"old", "data":"重複"}
]
次のタイミングでJSONを取得すると、データが追加されたり削除されてたりする。
[
{"id":"01", "name":"山田", "obj":"new", "data":"重複"},
{"id":"03", "name":"神山", "obj":"new", "data":""},
{"id":"04", "name":"森岡", "obj":"new", "data":"重複"},
{"id":"05", "name":"稲田", "obj":"new", "data":"重複"},
{"id":"06", "name":"関谷", "obj":"new", "data":""},
{"id":"07", "name":"寺島", "obj":"new", "data":""},
{"id":"08", "name":"浅川", "obj":"new", "data":""},
{"id":"10", "name":"益子", "obj":"new", "data":"重複"}
]
まぁよくありがちだけど、この差分を出したい(oldから削除され、newで追加されたもの)と思った。ほんと急な思いつきだった。
JSONが
[
{"name":"A", "data":"いろは"},
{"name":"B", "data":"あいう"},
{"name":"C", "data":"ABC"}
]
から
[
{"name":"A", "data":"いろは"},
{"name":"C", "data":"ABC"},
{"name":"D", "data":"123"}
]
に変わったとき、BとCを出すにはどーするかなぁ。— (ᐛ)みせる (@micelle9) 12 June 2018
悩んだ結果テキトーにJavaScriptを書いた。DOM操作してないのでjQueryは未使用。
なお、今回差分を出すにあたって「ID」があるか無いかしか見ていないので、汎用性には乏しいかと思う。(nameやdataなどが変わってても同一として見なしちゃうからね。その辺は適当に弄ればなんとかなるはず)
テキトーなコード
var oldObj = [
{"id":"01", "name":"山田", "obj":"old", "data":"重複"}, // 0
{"id":"02", "name":"野田", "obj":"old", "data":""},
{"id":"04", "name":"森岡", "obj":"old", "data":"重複"}, // 2
{"id":"05", "name":"稲田", "obj":"old", "data":"重複"}, // 3
{"id":"09", "name":"杉山", "obj":"old", "data":""},
{"id":"10", "name":"益子", "obj":"old", "data":"重複"} // 5
];
var newObj = [
{"id":"01", "name":"山田", "obj":"new", "data":"重複"}, // 0
{"id":"03", "name":"神山", "obj":"new", "data":""},
{"id":"04", "name":"森岡", "obj":"new", "data":"重複"}, // 2
{"id":"05", "name":"稲田", "obj":"new", "data":"重複"}, // 3
{"id":"06", "name":"関谷", "obj":"new", "data":""},
{"id":"07", "name":"寺島", "obj":"new", "data":""},
{"id":"08", "name":"浅川", "obj":"new", "data":""},
{"id":"10", "name":"益子", "obj":"new", "data":"重複"} // 7
];
var res = objectDiff(oldObj, newObj);
console.log('--旧--');
console.log(oldObj);
console.log('--新--');
console.log(newObj);
console.log('--結果--');
console.log(res);
function objectDiff(objA, objB) {
// 重複している箇所を配列へ保存
var ASpl = [];
var BSpl = [];
for (var n = 0, m = objA.length; n < m; n++) {
var ATrg = objA[n].id;
for (var i = 0, j = objB.length; i < j; i++) {
var BTrg = objB[i].id;
if (ATrg == BTrg) {
//console.log(`一致ID:${ATrg} / objA(n):${n}番目 / objB(i):${i}番目`);
ASpl.push(n);
BSpl.push(i);
}
}
}
//console.log(`ASpl:${ASpl} / BSpl:${BSpl}`);
// objをコピーし、重複した箇所を後ろから削除する
var objA_diff = objA.slice();
var objB_diff = objB.slice();
diffObj(objA_diff, ASpl);
diffObj(objB_diff, BSpl);
function diffObj(obj, spl) {
for (var n = spl.length - 1; n >= 0; n--) obj.splice(spl[n], 1);
}
// objから重複した箇所を抜き出す
var objA_lap = overlapObj(objA, ASpl);
var objB_lap = overlapObj(objB, BSpl);
function overlapObj(obj, spl) {
var ary = []
for (var n = 0, m = spl.length; n < m; n++) {
var num = spl[n];
ary.push(obj[num]);
}
return ary;
}
// オブジェクトを作り返す
var obj = {
"old": {"diff": objA_diff, "lap": objA_lap},
"new": {"diff": objB_diff, "lap": objB_lap}
};
return obj;
}
結果は以下のような感じになります(Chromeのconsoleで)
IDを元に差分・重複を出力することができた。まんぞく pic.twitter.com/7tC7QXUVEi
— (ᐛ)みせる (@micelle9) 12 June 2018
テキトーなかいせつ
var oldObj = [
{"id":"01", "name":"山田", "obj":"old", "data":"重複"}, // 0
{"id":"02", "name":"野田", "obj":"old", "data":""},
{"id":"04", "name":"森岡", "obj":"old", "data":"重複"}, // 2
{"id":"05", "name":"稲田", "obj":"old", "data":"重複"}, // 3
{"id":"09", "name":"杉山", "obj":"old", "data":""},
{"id":"10", "name":"益子", "obj":"old", "data":"重複"} // 5
];
var newObj = [
{"id":"01", "name":"山田", "obj":"new", "data":"重複"}, // 0
{"id":"03", "name":"神山", "obj":"new", "data":""},
{"id":"04", "name":"森岡", "obj":"new", "data":"重複"}, // 2
{"id":"05", "name":"稲田", "obj":"new", "data":"重複"}, // 3
{"id":"06", "name":"関谷", "obj":"new", "data":""},
{"id":"07", "name":"寺島", "obj":"new", "data":""},
{"id":"08", "name":"浅川", "obj":"new", "data":""},
{"id":"10", "name":"益子", "obj":"new", "data":"重複"} // 7
];
var res = objectDiff(oldObj, newObj);
特に語ることはない。
配列を変数へ入れvar res = objectDiff(oldObj, newObj);
の関数で返り値を待つだけ。
コメントアウトしてる// 0
は配列の何番目が重複しているのかメモってるだけです。後(というか次)で出てきます。
function objectDiff(objA, objB) {
// 重複している箇所を配列へ保存
var ASpl = [];
var BSpl = [];
for (var n = 0, m = objA.length; n < m; n++) {
var ATrg = objA[n].id;
for (var i = 0, j = objB.length; i < j; i++) {
var BTrg = objB[i].id;
if (ATrg == BTrg) {
//console.log(`一致ID:${ATrg} / objA(n):${n}番目 / objB(i):${i}番目`);
ASpl.push(n);
BSpl.push(i);
}
}
}
//console.log(`ASpl:${ASpl} / BSpl:${BSpl}`);
var res = objectDiff(oldObj, newObj);
で送られてきたobjAとobjBの重複している箇所を配列で取得しています。
forでobjAの1つめを見るとき、objBをforで全てを回して一致しているかを確認。あれば重複してる箇所(index)を配列へ保存…って感じです。
forとforを入れ子にしているので「objAの数×objBの数」分だけ試行されるので正直ヤバい。30件程度であれば30*30の900回で済むのでさくっと終わるけど、モノによっては500件と500件あるので…あへぇ。(もうjsじゃない方が良いのでは…?わかりませんが!!!)
ちなconsole.log(`一致ID:${ATrg} / objA(n):${n}番目 / objB(i):${i}番目`);
のコメントアウトを外すことで「一致したID&それがobjA・objBの何番目か」がログに出されます。
一致ID:01 / objA(n):0番目 / objB(i):0番目
一致ID:04 / objA(n):2番目 / objB(i):2番目
一致ID:05 / objA(n):3番目 / objB(i):3番目
一致ID:10 / objA(n):5番目 / objB(i):7番目
またconsole.log(`ASpl:${ASpl} / BSpl:${BSpl}`);
のコメントアウトを外すと
ASpl:0,2,3,5 / BSpl:0,2,3,7
と配列の中身を見ることが出来ます(オブジェクトの状態で見たければconsole.log(ASpl);
とかに書き換えてください)
うーん、log()の説明いらないな??
// objをコピーし、重複した箇所を後ろから削除する
var objA_diff = objA.slice();
var objB_diff = objB.slice();
diffObj(objA_diff, ASpl);
diffObj(objB_diff, BSpl);
function diffObj(obj, spl) {
for (var n = spl.length - 1; n >= 0; n--) obj.splice(spl[n], 1);
}
objAとobjBをslice()を利用してコピーします。
なぜコピーするかって?obj.splice(spl[n], 1);
を行うと、元々存在したobjAとobjBが破壊されるからです…(そうすると後々困るので、、、)
コピーした「objA_diffとobjB_diff」に対して「ASplとBSpl」を利用して、1つずつ重複した配列を削除していきます。なお、配列の削除は必ず後ろから行います。(前からやると順番ズレるぞ!!私はそれで失敗したぞ!!!
// objから重複した箇所を抜き出す
var objA_lap = overlapObj(objA, ASpl);
var objB_lap = overlapObj(objB, BSpl);
function overlapObj(obj, spl) {
var ary = []
for (var n = 0, m = spl.length; n < m; n++) {
var num = spl[n];
ary.push(obj[num]);
}
return ary;
}
今度は逆に重複した箇所を抜き出します。まぁ抜き出すと言っても、重複した配列を新しい配列へpush()してそれを返す。ってだけすけど。
// オブジェクトを作り返す
var obj = {
"old": {"diff": objA_diff, "lap": objA_lap},
"new": {"diff": objB_diff, "lap": objB_lap}
};
return obj;
そして最期に「旧データの差分・重複」と「新データの差分・重複」を配列にして返す。
以上です。
オッシャァ!!!
〆
objectDiff(objA, objB)
をobjectDiff(objA, objB ,trg)
とかにして(trgはString)、どこを重複のキーにするか指定させてあげると汎用性高まるのでは?(っつても最初に書いた例のようなJSONじゃないと無意味かと…)
「差分」といってもヒトによって異なるから、その時々に合わせて書き換えていくしか無いっすね。
というかJavaScript以外でやった方がスピード速いのでは????()
ディスカッション
コメント一覧
まだ、コメントがありません