To explain, look at the object below as it is being changed:
obj = {'a': 1, 'b': 2} // Version 1
obj['a'] = 2 // Version 2
obj['c'] = 3 // Version 3
I want to be able to get any of these versions of the object, for e.g. get obj
as of version 2. I don't want to store copies of the entire object every single time I want to update a single key.
How can I achieve this functionality?
The actual object I'm trying to do this with has about 500,000 keys. That's why I don't want to store entire copies of it with every update. My preferred language that this theoretical solution should be coded in are python
or javascript
, but I'll take anything.
About Object Versioning. Object versioning is enabled at the bucket level. Versioning directs Object Storage to automatically create an object version each time a new object is uploaded, an existing object is overwritten, or when an object is deleted.
Sign in to the AWS Management Console and open the Amazon S3 console at https://console.aws.amazon.com/s3/ . In the Buckets list, choose the name of the bucket that contains the object. In the Objects list, choose the name of the object. Choose Versions.
Set Object Versioning on a bucket. Note: Object Versioning cannot be enabled on a bucket that currently has a retention policy. In the Google Cloud console, go to the Cloud Storage Buckets page. In the list of buckets, click on the name of the bucket on which you want to enable or disable Object Versioning.
The examples in this section show how to retrieve an object listing from a versioning-enabled bucket. Each request returns up to 1,000 versions, unless you specify a lower number. If the bucket contains more versions than this limit, you send a series of requests to retrieve the list of all versions.
You could use ES6 proxies for that. These would trap any read/write operation on your object and log each change in a change log that can be used for rolling changes back and forward.
Below is a basic implementation, which might need some more features if you intend to apply other than basic update operations on your object. It allows to get the current version number and move the object back (or forward) to a specific version. Whenever you make a change to the object, it is first moved to its latest version.
This snippet shows some operations, like changing a string property, adding to an array, and shifting it, while moving back and forward to other versions.
Edit: It now also has capability to get the change log as an object, and apply that change log to the initial object. This way you can save the JSON of both the initial object and the change log, and replay the changes to get the final object.
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; }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With