【问题标题】:How to version control an object?如何对对象进行版本控制?
【发布时间】:2017-03-22 16:34:04
【问题描述】:

为了解释,请看下面正在更改的对象:

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

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

我怎样才能实现这个功能?

我尝试使用的实际对象有大约 500,000 个键。这就是为什么我不想在每次更新时都存储它的整个副本。我对这个理论解决方案的首选语言是pythonjavascript,但我会接受。

【问题讨论】:

  • 您想将它存储在哪里以及如何检索它?当你说“版本控制”时,我首先想到的是 git。它不在乎它是json还是其他格式。它只会保存差异。再说一次,当你说 500 000 个名称:值对时,我会说数据库听起来是个好主意。
  • 请注意,您所说的对象不是 JSON。您在该代码示例中定义和更改的是一个 JavaScript 对象;不是 JSON。注意:StackOverfow 不适用于请求有关库的建议(请参阅stackoverflow.com/help/on-topic)。
  • @zvone,我目前有一个数据库系统可以很好地保存日志和版本控制数据,但我发现它非常慢。我在代码中寻找一种算法,可以对 json 对象进行版本控制,然后将 json 对象作为一个整体存储在数据库中。
  • @trincot,我不知道为图书馆请求建议是题外话。感谢您指出这一点。

标签: javascript python json


【解决方案1】:

您可以为此使用 ES6 代理。这些将捕获对您的对象的任何读/写操作,并将每个更改记录在更改日志中,该更改日志可用于前后滚动更改。

以下是一个基本实现,如果您打算在对象上应用除基本更新操作之外的其他功能,则可能需要更多功能。它允许获取当前版本号并将对象向后(或向前)移动到特定版本。每当您对对象进行更改时,它都会首先移动到其最新版本。

这个 sn-p 显示了一些操作,例如更改字符串属性、添加到数组和移动它,同时来回移动到其他版本。

编辑:它现在还可以将更改日志作为对象获取,并将该更改日志应用到初始对象。这样您就可以保存初始对象和更改日志的 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; }

【讨论】:

  • 所以它基本上是一个存储库?
  • 这就像一个游戏:你可以玩棋子,也可以把它们拿回来,然后再次向前移动到最后记录的棋子。
  • @trincot,我对 ES6 语法不太熟悉。我仍在尝试了解代码库,并将回复您。谢谢你这么详细的回答。
  • 这不是错误,@Hieu。就是这样打算的。设置后,必须在两次连续调用update 时保留该状态,第一次调用设置length,第二次设置数组元素值。否则,gotoVersion 函数在恢复到旧版本时不会正确地将数组长度设置回原来的值。还打算将savedLength 变为undefined
  • 另外,在我的代码中重复更新是必要的,因为 splice 不会导致 single 版本更新:每次插入数组值都会触发一个单独的版本,所以我的回答中确实需要对savedLength 的个人更新。但是,这是我 4 年前写的,所以我不再那么熟悉了。
【解决方案2】:

您不需要保存整个对象。

只是差异。每个版本。

此函数将使用 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;
}, {});

您需要保留第一个对象,但我认为您可以通过这种方式保留版本。

【讨论】:

  • 感谢您的解决方案。我的计划是将此功能包含在上述解决方案中,以允许对密钥进行批量更新的版本控制。
【解决方案3】:

改用 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 ) 是一个克隆,但数据本身没有被复制或变异,因此它非常适合版本控制、保存要撤消的操作列表等。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-10-13
    • 2011-12-09
    • 2015-03-08
    • 2013-06-11
    • 2023-03-29
    • 2013-08-30
    • 2019-06-29
    • 2018-02-16
    相关资源
    最近更新 更多