如何控制对象的版本?

时间:2016-11-08 22:07:10

标签: javascript python json

要解释一下,请在下面的对象中进行更改:

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

我希望能够获得该对象的任何这些版本,例如从版本2开始获取obj。我不希望每次想要更新单个密钥时都存储整个对象的副本。

如何实现此功能?

我正在尝试使用的实际对象有大约500,000个键。这就是为什么我不希望每次更新都存储整个副本。我理解这个理论解决方案的首选语言是pythonjavascript,但我会采取任何措施。

3 个答案:

答案 0 :(得分:9)

您可以使用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; }

答案 1 :(得分: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;
}, {});

你需要保留第一个对象,但我认为你可以保留这种版本。

答案 2 :(得分:0)

改为使用Map对象:

const obj = new Map( [
    ['name', 'bob'],['number',2]
])
const v2 = ( new Map( obj ) ).set('name','suzie')
const v3 = ( new Map( v2 ) ).set('value',3)

// obj is 'name'->'bob', 'number'->2
// v2 is  'name'->'suzie', 'number'->2
// v3 is  'name'->'suzie', 'number'->2, 'value'->3

新的Map(obj)是一个克隆,但是数据本身不会被复制或突变,因此非常适合版本控制,保存要撤消的操作列表等。