9

The structured clone algorithm is a serialization algorithm used, among other things, to pass data between windows via window.postMessage. It supports recursive objects (unlike JSON) but not things like DOM Nodes, Functions, and Errors, and other

What I'd like is a simple way to check if a given object can be serialized by the structured clone algorithm. I could recursively walk the object and check if each property is a DOM Node, Function, or Error, but that's not a complete answer, and I was wondering if there was a better way.

Retsam
  • 30,909
  • 11
  • 68
  • 90
  • 5
    Why not try, then catch the `DATA_CLONE_ERR` exception that it throws if it's not valid? If you catch an exception, the object is not serializable, otherwise it is. – Maximillian Laumeister Sep 19 '15 at 21:48
  • 1
    The API doesn't always use `window.postMessage` depending on the context, but for consistency I'd like it to report an error if the data isn't compatible with the structured clone algorithm. – Retsam Sep 19 '15 at 22:20
  • 1
    Try/catch should work fine in that case too. – Maximillian Laumeister Sep 19 '15 at 22:23

1 Answers1

5

From the spec, I think it would be something like

function canBeCloned(val) {
  if(Object(val) !== val) // Primitive value
    return true;
  switch({}.toString.call(val).slice(8,-1)) { // Class
    case 'Boolean':     case 'Number':      case 'String':      case 'Date':
    case 'RegExp':      case 'Blob':        case 'FileList':
    case 'ImageData':   case 'ImageBitmap': case 'ArrayBuffer':
      return true;
    case 'Array':       case 'Object':
      return Object.keys(val).every(prop => canBeCloned(val[prop]));
    case 'Map':
      return [...val.keys()].every(canBeCloned)
          && [...val.values()].every(canBeCloned);
    case 'Set':
      return [...val.keys()].every(canBeCloned);
    default:
      return false;
  }
}

Note this has some limitations:

  • I can't check if an object has a [[DataView]] internal slot
  • {}.toString is not a reliable way to get the [[Class]], but is the only one.
  • Other specifications may define how to clone additional kinds of objects

So it may be more reliable to attempt to run the algorithm, and see if it produces some error:

function canBeCloned(val) {
  try {
    window.postMessage(val,'*');
  } catch(err) {
    return false;
  }
  return true;
}

Note if you have a message event listener, it will be called. If you want to avoid this, send the value to another window. For example, you can create one using an iframe:

var canBeCloned = (function() {
  var iframe = document.createElement('iframe');
  document.body.appendChild(iframe);
  var win = iframe.contentWindow;
  document.body.removeChild(iframe);
  return function(val) {
    try { win.postMessage(val, '*'); }
    catch(err) { return false; }
    return true;
  };
})();
Oriol
  • 274,082
  • 63
  • 437
  • 513
  • I don't think `|| val instanceof Object` does what you intended to do – Bergi Sep 19 '15 at 22:55
  • @Bergi True, most host objects have `Object.prototype` in the prototype chain. The spec says "*If input is an `Object` object*", not sure if that means the [[Prototype]] must be exactly `Object.prototype`, or [[Class]] `"Object"` – Oriol Sep 20 '15 at 21:40
  • Yeah, the [spec](http://www.w3.org/html/wg/drafts/html/master/infrastructure.html#safe-passing-of-structured-data) mentions (below the list of conditions) that they mean the `[[class]]` – Bergi Sep 20 '15 at 21:44
  • [MessagePort](https://developer.mozilla.org/en-US/docs/Web/API/MessagePort) might also be useful for the "try and see" approach, though this approach could be problematic if you don't do something to avoid spamming the console with error messages when cloning fails... – SamB Jul 18 '19 at 14:49
  • 2
    If the object contains cyclic references, won't this function overflow the stack? – Ben Steward Dec 12 '19 at 07:32