2つのJavaScriptのobjectの差分が取れる。JSONの差分が取れるCLIツールもついている。
https://www.npmjs.com/package/jsondiffpatch


元データ


wikiのページのようなデータを2つ用意する。増えた行、減った行、少し編集された行がある。
let page = {
title: "ruby",
lines: [
{text: "プログラム言語", user: "shokai"},
{text: "http://ruby-lang.org", user: "shokai"},
{text: "111", user: "shokai"},
{text: "2222", user: "shokai"},
{text: "33333", user: "shokai"},
{text: "444444", user: "shokai"}
],
updatedAt: Date.now()
};

let page2 = {
title: "ruby",
lines: [
{text: "プログラミング言語", user: "shokai"},
{text: "テキスト処理に便利", user: "kazusuke"},
{text: "http://ruby-lang.org", user: "shokai"},
{text: "111", user: "shokai"},
{text: "2222", user: "shokai"},
{text: "33333", user: "shokai"}
],
updatedAt: Date.now() + 300000
};


差分を取る、差分をパッチとして使う


jsondiffpatch.diff(left, right) で差分が取れる。patch、unpatchでちゃんと変更を適用できるか確かめる。
配列内のオブジェクトの比較のためにobjectHashという関数が必要。これはオブジェクトに持たせるのではなく、jsondiffpatch.createに引数として渡す。配列の比較をしない場合は必要ない。
https://github.com/benjamine/jsondiffpatch/blob/master/docs/arrays.md
また、patchとunpatch関数はパッチを当てた元データを破壊的に処理してしまうので、cloneしてからpatch/unpatchするようにした。

import {assert} from "chai";
import * as JSONDiffPatch from "jsondiffpatch";
import md5 from "md5"

const jsondiffpatch = JSONDiffPatch.create({
// https://github.com/benjamine/jsondiffpatch/blob/master/docs/arrays.md
objectHash: (obj, index) => {
return md5(obj.text + obj.user);
}
});

// patch/unpatchが元データを破壊的に処理する為必要
function clone(obj){
return JSON.parse(JSON.stringify(obj));
}

let diff = jsondiffpatch.diff(page, page2);

console.log(JSON.stringify(diff, null, 2));

// patchあててもちゃんと同一になる
assert.deepEqual(jsondiffpatch.patch(clone(page), diff), page2);

// patchあててpatch戻す
assert.deepEqual(jsondiffpatch.unpatch(clone(page2), diff), page);

assert.deepEqual(
jsondiffpatch.unpatch(
jsondiffpatch.patch(clone(page), diff)
, diff)
, page);


diffはdeltaというフォーマットになっている。差分なので元データにある1111や2222などの行は含まれない。
{
"lines": {
"0": [
{
"text": "プログラミング言語",
"user": "shokai"
}
],
"1": [
{
"text": "テキスト処理に便利",
"user": "kazusuke"
}
],
"_t": "a",
"_0": [
{
"text": "プログラム言語",
"user": "shokai"
},
0,
0
],
"_5": [
{
"text": "444444",
"user": "shokai"
},
0,
0
]
},
"updatedAt": [
1457784139836,
1457784439836
]
}


MongoDBで使う

まだ試してないけどmongooseのプラグインで、pre-saveで保存前後の差分を取得できるやつがある。
https://www.npmjs.com/package/mongoose-diff

これを使うと最新データのみ保存して、古いデータは変更履歴のみ保存するといったことが楽に実装できそう。