javascriptstructured-clone

How to check if an object can be cloned by the structured clone algorithm


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.


Solution

  • 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:

    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;
      };
    })();