2016-11-08 68 views
4

講解,看看下面的對象,因爲它正在改變:如何版本控制一個對象?

obj = {'a': 1, 'b': 2} // Version 1 
obj['a'] = 2 // Version 2 
obj['c'] = 3 // Version 3 

我希望能夠得到任何這些版本的對象的,對於例如從版本2開始獲得obj。我不想在每次更新單個密鑰時都存儲整個對象的副本。

如何實現此功能?

我試圖做到這一點的實際對象有大約500,000個按鍵。這就是爲什麼我不想在每次更新時都存儲整個副本。我的理論解決方案應該編碼的首選語言是pythonjavascript,但我會採取任何措施。

+0

你想去哪兒來存儲和你想如何找回?當你說「版本控制」時,我的第一個想法就是git。它不會在乎它是否是json或其他格式。它只會節省差異。再說一次,當你說500 000個名稱:值對時,我會說數據庫聽起來像個好主意。 – zvone

+0

請注意,您所說的對象不是JSON。在該代碼示例中定義和更改的是JavaScript對象;不是JSON。注意:StackOverfow不適用於請求庫的建議(請參閱http://stackoverflow.com/help/on-topic)。 – trincot

+0

@zvone,我目前有一個數據庫系統,可以很好地保存日誌和版本控制數據,但是我發現它非常慢。我在代碼中尋找可以版本控制json對象的算法,然後將json對象作爲一個整體存儲在數據庫中。 – josneville

回答

6

您可以使用ES6代理。這些可以捕獲對象的任何讀/寫操作,並記錄可用於前後滾動更改的更改日誌中的每個更改。

下面是一個基本實現,如果您打算在對象上應用除基本更新操作以外的其他功能,則可能需要更多功能。它允許獲取當前版本號並將對象移回(或轉發)到特定版本。無論何時對對象進行更改,都會先移至最新版本。

該代碼片段顯示了一些操作,例如更改字符串屬性,添加到數組以及移動它,同時前後​​移動到其他版本。

編輯:它現在還能夠將更改日誌作爲對象獲取,並將該更改日誌應用於初始對象。這樣,您可以保存初始對象和更改日誌的JSON,並重播更改以獲取最終對象。

function VersionControlled(obj, changeLog = []) { 
 
    var targets = [], version = 0, savedLength, 
 
     hash = new Map([[obj, []]]), 
 
     handler = { 
 
      get: function(target, property) { 
 
       var x = target[property]; 
 
       if (Object(x) !== x) return x; 
 
       hash.set(x, hash.get(target).concat(property)); 
 
       return new Proxy(x, handler); 
 
      }, 
 
      set: update, 
 
      deleteProperty: update 
 
     }; 
 

 
    function gotoVersion(newVersion) { 
 
     newVersion = Math.max(0, Math.min(changeLog.length, newVersion)); 
 
     var chg, target, path, property, 
 
      val = newVersion > version ? 'newValue' : 'oldValue'; 
 
     while (version !== newVersion) { 
 
      if (version > newVersion) version--; 
 
      chg = changeLog[version]; 
 
      path = chg.path.slice(); 
 
      property = path.pop(); 
 
      target = targets[version] || 
 
        (targets[version] = path.reduce ((o, p) => o[p], obj)); 
 
      if (chg.hasOwnProperty(val)) { 
 
       target[property] = chg[val]; 
 
      } else { 
 
       delete target[property]; 
 
      } 
 
      if (version < newVersion) version++; 
 
     } 
 
     return true; 
 
    } 
 
    
 
    function gotoLastVersion() { 
 
     return gotoVersion(changeLog.length); 
 
    } 
 
    
 
    function update(target, property, value) { 
 
     gotoLastVersion(); // only last version can be modified 
 
     var change = {path: hash.get(target).concat([property])}; 
 
     if (arguments.length > 2) change.newValue = value; 
 
     // Some care concerning the length property of arrays: 
 
     if (Array.isArray(target) && +property >= target.length) { 
 
      savedLength = target.length; 
 
     } 
 
     if (property in target) { 
 
      if (property === 'length' && savedLength !== undefined) { 
 
       change.oldValue = savedLength; 
 
       savedLength = undefined; 
 
      } else { 
 
       change.oldValue = target[property]; 
 
      } 
 
     } 
 
     changeLog.push(change); 
 
     targets.push(target); 
 
     return gotoLastVersion(); 
 
    } 
 
    
 
    this.data = new Proxy(obj, handler); 
 
    this.getVersion = _ => version; 
 
    this.gotoVersion = gotoVersion; 
 
    this.gotoLastVersion = gotoLastVersion; 
 
    this.getChangeLog = _ => changeLog; 
 
    // apply change log 
 
    gotoLastVersion(); 
 
} 
 

 
// sample data 
 
var obj = { list: [1, { p: 'hello' }, 3] }; 
 

 
// Get versioning object for it 
 
var vc = new VersionControlled(obj); 
 
obj = vc.data; // we don't need the original anymore, this one looks the same 
 

 
// Demo of actions: 
 
console.log(`v${vc.getVersion()} ${JSON.stringify(obj)}. Change text:`); 
 
obj.list[1].p = 'bye'; 
 
console.log(`v${vc.getVersion()} ${JSON.stringify(obj)}. Bookmark & add property:`); 
 
var bookmark = vc.getVersion(); 
 
obj.list[1].q = ['added']; 
 
console.log(`v${vc.getVersion()} ${JSON.stringify(obj)}. Push on list, then shift:`); 
 
obj.list.push(4); // changes both length and index '4' property => 2 version increments 
 
obj.list.shift(); // several changes and a deletion 
 
console.log(`v${vc.getVersion()} ${JSON.stringify(obj)}. Go to bookmark:`); 
 
vc.gotoVersion(bookmark); 
 

 
console.log(`v${vc.getVersion()} ${JSON.stringify(obj)}. Go to last version:`); 
 
vc.gotoLastVersion(); 
 
console.log(`v${vc.getVersion()} ${JSON.stringify(obj)}. Get change log:`); 
 
var changeLog = vc.getChangeLog(); 
 
for (var chg of changeLog) { 
 
    console.log(JSON.stringify(chg)); 
 
} 
 

 
console.log('Restart from scratch, and apply the change log:'); 
 
obj = { list: [1, { p: 'hello' }, 3] }; 
 
vc = new VersionControlled(obj, changeLog); 
 
obj = vc.data; 
 
console.log(`v${vc.getVersion()} ${JSON.stringify(obj)}`);
.as-console-wrapper { max-height: 100% !important; top: 0; }

+0

所以它基本上是一個存儲庫? –

+0

它就像一個遊戲:你可以玩移動,但也可以拿回來,然後再向前移動到最後記錄的移動。 – trincot

+0

@trincot,我不熟悉ES6語法。我仍然試圖瞭解代碼庫,並會回覆你。謝謝你的詳細回答。 – josneville

0

你不需要保存整個對象。

只是差異。對於每個版本。

該函數將使用lodash進行深度比較,並將返回舊對象和新對象之間的差異。

var allkeys = _.union(_.keys(obj1), _.keys(obj2)); 
var difference = _.reduce(allkeys, function (result, key) { 
    if (!_.isEqual(obj1[key] !== obj2[key])) { 
    result[key] = {obj1: obj1[key], obj2: obj2[key]} 
    } 
    return result; 
}, {}); 

您需要保留第一個對象,但我認爲您可以保留這些版本。

+0

感謝您的解決方案。我的計劃是將此功能包含在上述解決方案中,以允許批量更新密鑰的版本。 – josneville