10

Is there a generic approach to "compressing" nested objects to a single level:

var myObj = {
    a: "hello",
    b: {
        c: "world"
    }
}

compress(myObj) == {
    a: "hello",
    b_c: "world"
}

I guess there would be some recursion involved, but I figured I don't need to reinvent the wheel here... !?

3
  • Why would you need that? Do you want to process your javascripts during build time and then runtime js performance will be improved? But how would you access nested objects from your js (after compressing) if you need to operate with complex object model (where for example subobject (nested object) has to passes as an argument to some function)? Commented Jun 8, 2009 at 6:34
  • 1
    I need this for a data mapping where the processing does not handle nested objects. Commented Jun 8, 2009 at 6:48
  • I had a similar need to AnC when using nested objects with Redis as it only supports flat hashes. I ended up using a CoffeeScript version of Matthew Crumley's solution. Commented Aug 4, 2011 at 13:19

3 Answers 3

21
function flatten(obj, includePrototype, into, prefix) {
    into = into || {};
    prefix = prefix || "";

    for (var k in obj) {
        if (includePrototype || obj.hasOwnProperty(k)) {
            var prop = obj[k];
            if (prop && typeof prop === "object" &&
                !(prop instanceof Date || prop instanceof RegExp)) {
                flatten(prop, includePrototype, into, prefix + k + "_");
            }
            else {
                into[prefix + k] = prop;
            }
        }
    }

    return into;
}

You can include members inherited members by passing true into the second parameter.

A few caveats:

  • recursive objects will not work. For example:

    var o = { a: "foo" };
    o.b = o;
    flatten(o);
    

    will recurse until it throws an exception.

  • Like ruquay's answer, this pulls out array elements just like normal object properties. If you want to keep arrays intact, add "|| prop instanceof Array" to the exceptions.

  • If you call this on objects from a different window or frame, dates and regular expressions will not be included, since instanceof will not work properly. You can fix that by replacing it with the default toString method like this:

    Object.prototype.toString.call(prop) === "[object Date]"
    Object.prototype.toString.call(prop) === "[object RegExp]"
    Object.prototype.toString.call(prop) === "[object Array]"
    
Sign up to request clarification or add additional context in comments.

1 Comment

Wow, this seems to work great! Thanks a bunch, also for the detailed documentation - I really appreciate it!
4

Here's a quick one, but watch out, b/c it will not work w/ arrays and null values (b/c their typeof returns "object").

var flatten = function(obj, prefix) {
  if(typeof prefix === "undefined") {
    prefix = "";
  }
  var copy = {};
  for (var p in obj) {
    if(obj.hasOwnProperty(p)) {
      if(typeof obj[p] === "object") {
        var tmp = flatten(obj[p], p + "_");
        for(var q in tmp) {
          if(tmp.hasOwnProperty(q)) {
            copy[prefix + q] = tmp[q];
          }
        }
      }
      else {
        copy[prefix + p] = obj[p];
      }
    }
  }
  return copy;
}

var myObj = {
  a: "level 1",
  b: {
    a: "level 2",
    b: {
      a: "level 3",
      b: "level 3"
    }
  }
}

var flattened = flatten(myObj);

1 Comment

Thanks for this. It doesn't quite work yet (see test object below); will dig into it and report any progress here. (As mentioned before, I expected this was a solved problem - i.e. that there'd be a ready-made function in some JavaScript cookbook... ) var myObj = { a1: "level 1", a2: { b1: 99, b2: { c1: new Date(), c2: "level 3" }, b3: "asd" }, a3: /foo/ };
2

Here's a quick CoffeeScript version based off Matthew Crumley's answer (I didn't use includePrototype as I had no need for it):

flatten = (obj, into = {}, prefix = '', sep = '_') ->
  for own key, prop of obj
    if typeof prop is 'object' and prop not instanceof Date and prop not instanceof RegExp
      flatten prop, into, prefix + key + sep, sep
    else
      into[prefix + key] = prop
  into

And a basic unflatten version, which would undoubtedly fail with repeated separators and other such trickiness:

unflatten = (obj, into = {}, sep = '_') ->
  for own key, prop of obj
    subKeys = key.split sep
    sub = into
    sub = (sub[subKey] or= {}) for subKey in subKeys[...-1]
    sub[subKeys.pop()] = prop
  into

FWIW, I use these functions to push object graphs into Redis hashes, which only support a single depth of key/value pairs.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.