striking
striking

Reputation: 663

How do I make a checksum from a JavaScript Object?

I need to make a checksum from a JavaScript Object.
Unfortunately, there does not seem to be an easy way to accomplish this because of JavaScript's Object ordering. For example, take these Objects:

var obj1 = {type:"cake",quantity:0}
  , obj2 = {quantity:0,type:"cake"};

I consider these Objects equal in data, and would like their checksums to be the same. I really don't care about the order of the Object just as long as the data in them is the same.
Alas, JSON.stringify of both of them is actually not equal; as the only way to make a checksum of an Object is via its String representation, and the JSON.stringify-ed representations are not equal, my checksums will not be equal!
One solution I have come up with is to recreate the Object based on a predefined schema, like so:

var schema = ["type","quantity"];
function sortify(obj,schema){
  var n={};
  for(i in schema)
    n[schema[i]]=obj[schema[i]];
  return n
}

Running JSON.stringify(sortify(obj1,schema))==JSON.stringify(sortify(obj2,schema)) will return true... but at the price of creating a new Object and shuffling around data.

My other solution is to replace the JSON.stringify method with one that picks keys from a predefined schema and stringifying their values, then joining them together. The function reads:

function smarterStringify(obj,schema){
  var s="";
  for(i in schema)
    s+=JSON.stringify(obj[schema[i]]);
  return s
}

Ignoring the fact that this method doesn't return correct JSON (it's close enough as an example of what I'm trying to do), it is a massive improvement over the first one in speed (at least in my Chrome OS browser, you can check it yourself here: http://jsperf.com/sort-then-json-stringify-vs-smarter-stringify), and of course it makes the two Object String representations equal!

However, I was just wondering if I had missed something and there was a built-in method for something like this all along that didn't a) drive the JavaScript GC into a pathological case or b) do way too many String concatenations. I'd rather not do those.

Upvotes: 12

Views: 38309

Answers (3)

Yan Foto
Yan Foto

Reputation: 11388

3 years later...

I came across this question as I wanted to hash my JSON objects to create Etags for my HTTP responses. So I ended up writing my own solution for Node, jsum, which boils down to a simple serializer:

/**
 * Stringifies a JSON object (not any randon JS object).
 *
 * It should be noted that JS objects can have members of
 * specific type (e.g. function), that are not supported
 * by JSON.
 *
 * @param {Object} obj JSON object
 * @returns {String} stringified JSON object.
 */
function serialize (obj) {
  if (Array.isArray(obj)) {
    return JSON.stringify(obj.map(i => serialize(i)))
  } else if (typeof obj === 'object' && obj !== null) {
    return Object.keys(obj)
      .sort()
      .map(k => `${k}:${serialize(obj[k])}`)
      .join('|')
  }

  return obj
}

You can then take the result and hash it using common algorithms (e.g. SHA256) or use the convenient method of digest from jsum package.


Please note the license here!

Upvotes: 11

jfriend00
jfriend00

Reputation: 708026

You can collect the keys into an array with Object.keys(), sort that array and then checksum the keys/values in that known, predictable order. I don't know of any way to use JSON.stringify() with all the sorted keys at once though so you'd have to do your own checksum.

I am not aware of any built-in method for something like this. Object keys are NOT guaranteed to be in any particular order so it would not be safe to rely on that.


If you don't have nested objects or arrays as property values, then you could do something like this:

// creates an array of alternating property name, property value
// with properties in sorted order
// then stringify's that array
function stringifyPropsInOrder(obj) {
    var keys = Object.keys(obj).sort();
    var output = [], prop;
    for (var i = 0; i < keys.length; i++) {
        prop = keys[i];
        output.push(prop);
        output.push(obj[prop]);
    }
    return JSON.stringify(output);
}

function compareObjects(a, b) {
    return stringifyPropsInOrder(a) === stringifyPropsInOrder(b);
}

If you want faster performance, you don't have to stringify (that was just done here to save code). You could just return the flattened output array and compare the arrays directly.


If you could have embedded objects as property values, then some more work has to do to recusively expand those into the same flattened array of properties/values.

Upvotes: 7

Fosco
Fosco

Reputation: 38526

You could also make a function which compares your objects in place:

function compareObjects(a, b) {
  var akeys = Object.keys(a);
  var bkeys = Object.keys(b);
  var len = akeys.length;
  if (len != bkeys.length) return false;
  for (var i = 0; i < len; i++) {
    if (a[akeys[i]] !== b[akeys[i]]) return false;
  }
  return true;
}

This assumes they are objects passed in, and that they're simple flat objects. Logic could be added to check these assumptions and recursively check if sub-objects are equal.

Upvotes: 6

Related Questions