This article is about a difference
algorithm. It extracts changes from one version of an object to another. It helps storing a smaller amount of information.
Template
In a project, I have a template object with all the default settings for a widget.
var template = { achievement: { page: 0, column: "name", ascending: true, search: "", filter: [1, 2, 3] } }; |
Alter it
Default settings are changed during the use of the application. It can happen when you load a configuration from an object:
var override = { achievement: { page: 1, ascending: false } }; var settings = _.extend({}, template, override); |
Or when the user clicks on the buttons, you have script that edits a copy of the template:
var settings = _.extend({}, template); settings.achievement.page = 1; settings.achievement.ascending = false; |
Find the differences
Now, we'd like to store the current user configuration. We could simply dump the current settings
variable but we can do better. Instead we want to dump only what has changed from the defaults. This has the benefit of being smaller to store and more resilient to changes.
In order to extract changes between the template and settings, I made a small function called difference
(If you have a better name, feel free to suggest :)).
var override = difference(template, settings); console.log(override); // Result: { achievement: { page: 1, ascending: false } } |
Instead of saving the full object, we only have to save 2 fields. This is therefore smaller 🙂 In order to get the full configuration back, we simply extend
the template object with the difference:
var settings = _.extend({}, template, override); |
In a way, difference
is the opposite of extend
.
settings = extend(template, override)
override = difference(template, settings)
How it works
Once we know what we want it to do, it is fairly straightforward to write. We traverse the template and build a new object that contains only attributes that mismatch between template
and override
. The following implementation makes great use of underscore (isObject, isArray and isEqual).
function difference(template, override) { var ret = {}; for (var name in template) { if (name in override) { if (_.isObject(override[name]) && !_.isArray(override[name])) { var diff = difference(template[name], override[name]); if (!_.isEmpty(diff)) { ret[name] = diff; } } else if (!_.isEqual(template[name], override[name])) { ret[name] = override[name]; } } } return ret; } |
Note: Any attribute that is present only in the override
object will be ignored.
Conclusion
I'm still looking forward storing as much information as possible in the hash part of the URL. Two years ago, I did SmallHash to encode integer ranges. This week, with URLON and this difference algorithm, I explored another way to look at the problem, dealing with structured objects.
It is possible to combine both approaches in order to encode structured objects that also contain integer ranges. Maybe in a next blog post!