Inverse of jQuery.extend(true, …)
Asked Answered
H

3

7

I'm looking to reduce storage requirements for JSON data by deltifying it against a known set of defaults. Basically, what I want is an inverse for jQuery's .extend() function, such that the following test passes for arbitrary JSON-compatible objects:

function test_delta(defaults, delta) {
    var current = $.extend(true, {}, defaults, delta);

    QUnit.same(get_delta(current, defaults), delta);
}

Before I start writing my own get_delta(), is anyone aware of an existing implementation?

Hypophosphate answered 4/8, 2010 at 21:43 Comment(6)
You'll have to handle this one yourself, the hard (nested loop) way. As you probably know. It's even more fun if your JSON data has arbitrary depth.Strahan
Yeah, I've come up with some interesting edge cases already; that's why I thought I'd ask the "SO oracle" before digging into it myself. Ah, well. :-)Hypophosphate
A good place to start would be looking at the jQuery.fn.extend function in jQuery source.Tithe
@Tithe — Oh, I'm already quite familiar with extend()'s innards, I was mostly just hoping not to have to write it myself. ;-)Hypophosphate
start with a copy/paste of extend? ;) I'd like to see this appear on github when you're doneEfik
You never will be able to reconstruct delta when the original one had some properties that were equal to the respective ones in defaults. No get_delta would work for both the arbitrary JSON tests test_delta({a:1}, {a:1}) and test_delta({a:1}, {}). However if you only tested QUnit.same($.extend(true, {}, defaults, get_delta(current, defaults)), current) there would be a trivial solution.Luminosity
C
1

What you're really looking for is an object diff(erential) algorithm.

Not too difficult to write -



function diff (obj1, obj2) {
   var delta = {};

   for (var x in obj1) {
       if (obj2.hasOwnProperty(x)) {
           if (typeof obj2[x] == "object") {
               //recurse nested objects/arrays
               delta[x] = diff(obj1[x], obj2[x]);
           }
           else {
               //if obj2 doesn't match then - modified attribute
               if (obj2[x] != obj1[x]) {
                   delta[x] = obj1[x];
               }
           }        
       }
       else {
           //obj2 doesn't have this - new attribute
           delta[x] = obj1[x];
       }
   }

   return delta;
}

alert( 
  JSON.stringify(
     diff({ hello : 'world', gone : 'fishing' }, 
          { hello : 'world' })
  )
);

//outputs:
{ gone : 'fishing' }

As you can see this is a very basic implementation - you could extend this to provide a complete differential by returning additions to obj2 in a separate object.

This code isn't bug free, object protoypes and functions will be handled differently in different browsers, but it should suffice as a demonstration for data structures.

Competent answered 13/12, 2010 at 22:46 Comment(1)
I did take a stab at implementing my own, but there were enough unclear edge cases (particularly with arrays and deleted properties) that we decided to simply enlarge the database column instead.Hypophosphate
R
1

Try something like that:

jQuery.extend({
    deltaExtend: function(deep, target, defaults, delta){
        var result = jQuery.extend.apply(jQuery, arguments);
        jQuery(result).data('delta', delta);
        jQuery(result).data('defaults', defaults);
        return result;
    }
});

usage:

var result = $.deltaExtend(true, {}, defaults, delta);
$(result).data('delta') //returns delta object
$(result).data('defaults') //returns default object

It also can be tweaked to get it working for N objects, just requires a little more thinking.

Ragsdale answered 22/2, 2011 at 17:17 Comment(2)
This is a very interesting idea! Unfortunately, $.extend is not used in creating the "result" object, in my case. This was for a portal system, where each portlet could define its own default settings. The "current" settings would then be modified freely over the lifetime of the page and, when time came to save the portlet's state back to the server, I was hoping to perform a "blind" delta to reduce the storage requirements.Hypophosphate
I have another solution in mind, what browsers are you supporting, and what versions, more specifically are u supporting IE < IE8 ?Ragsdale
A
1

I know this is a little late to be relevant to the topic starter, but you might want to have a look at the _.omit(object, *keys) function from Underscore.js. It does just that.

Anaximenes answered 29/4, 2014 at 22:40 Comment(3)
But how do you know the keys?Luminosity
The keys come from the object where the defaults are stored. _.keys(defaultObject) gives you an array of its keys.Anaximenes
You might should have mentioned that in your answer… And notice that this method only works for added properties, not for all changed ones.Luminosity

© 2022 - 2024 — McMap. All rights reserved.