How to stringify event object?
Asked Answered
T

7

46

JSON.stringify(eventObject);

gives:

TypeError: Converting circular structure to JSON


dojox.json.ref.toJson(eventObject);

gives:

TypeError: Accessing selectionEnd on an input element that cannot have a selection.


Is there some library/code ready to use to accomplish it ?

Tass answered 18/7, 2012 at 18:8 Comment(2)
Are you trying to view the structure to extract certain method(s) from the object or what is your purpose for wanting to stringify it?Chicken
debugging PhoneGap - send data structures, events and stack traces to native layerTass
T
22

You won't be able to serialize an event object with JSON.stringify, because an event object contains references to DOM nodes, and the DOM has circular references all over the place (e.g. child/parent relationships). JSON can't handle these by default, so you're a bit out of luck there.

I'd suggest to look at How to serialize DOM node to JSON even if there are circular references? which has a few suggestions on how to serialize a DOM node. Also, the following questions seem to have useful information:

JSON libraries able to handle circular references seem to be

Alternatively, you could delete all references to DOM nodes if you don't need them, and then serialize the object. You shouldn't do this after all. See @PointedEars comment :)

Towill answered 18/7, 2012 at 18:20 Comment(1)
Event objects are host objects. Do not mess with them, such as trying to delete their properties or assign to properties that are not supposed to be assigned to.Cipher
A
22

Use the "replacer" function to avoid errors:

JSON.stringify(evt, function(k, v) {
    if (v instanceof Node) {
        return 'Node';
    }
    if (v instanceof Window) {
        return 'Window';
    }
    return v;
}, ' ');

Update 2019: the browser API has changed some way, here is a method to expose all available keys in Event prototype chain

function stringifyEvent(e) {
  const obj = {};
  for (let k in e) {
    obj[k] = e[k];
  }
  return JSON.stringify(obj, (k, v) => {
    if (v instanceof Node) return 'Node';
    if (v instanceof Window) return 'Window';
    return v;
  }, ' ');
}
Archduchy answered 29/12, 2015 at 21:15 Comment(3)
this also results in only {isTrusted: true} for MouseEvent'sGraptolite
I'm downvoting this only because of the limited use of a function that returns "Node" instead of all the info you would normally want from an event object.Infelicitous
@PrestonBadeer You can create a custom algorithm that will handle circular references, return a constructor name, a selector etc. It's up to you.Archduchy
N
8

I had a similar problem and wrote a simple event serializer with a helper method to cleanup the event's path attribute. The approach for this solution to transform data from the event to a serializable object:

  • Copy over primitive attributes
  • Copy outerHTML for element attributes in the event object
  • Calculate selector path for the path attribute (this avoids copying the outerHTML of the entire HTML page)

// Calculate a string representation of a node's DOM path.
var pathToSelector = function(node) {
  if (!node || !node.outerHTML) {
    return null;
  }

  var path;
  while (node.parentElement) {
    var name = node.localName;
    if (!name) break;
    name = name.toLowerCase();
    var parent = node.parentElement;

    var domSiblings = [];

    if (parent.children && parent.children.length > 0) {
      for (var i = 0; i < parent.children.length; i++) {
        var sibling = parent.children[i];
        if (sibling.localName && sibling.localName.toLowerCase) {
          if (sibling.localName.toLowerCase() === name) {
            domSiblings.push(sibling);
          }
        }
      }
    }

    if (domSiblings.length > 1) {
      name += ':eq(' + domSiblings.indexOf(node) + ')';
    }
    path = name + (path ? '>' + path : '');
    node = parent;
  }

  return path;
};

// Generate a JSON version of the event.
var serializeEvent = function(e) {
  if (e) {
    var o = {
      eventName: e.toString(),
      altKey: e.altKey,
      bubbles: e.bubbles,
      button: e.button,
      buttons: e.buttons,
      cancelBubble: e.cancelBubble,
      cancelable: e.cancelable,
      clientX: e.clientX,
      clientY: e.clientY,
      composed: e.composed,
      ctrlKey: e.ctrlKey,
      currentTarget: e.currentTarget ? e.currentTarget.outerHTML : null,
      defaultPrevented: e.defaultPrevented,
      detail: e.detail,
      eventPhase: e.eventPhase,
      fromElement: e.fromElement ? e.fromElement.outerHTML : null,
      isTrusted: e.isTrusted,
      layerX: e.layerX,
      layerY: e.layerY,
      metaKey: e.metaKey,
      movementX: e.movementX,
      movementY: e.movementY,
      offsetX: e.offsetX,
      offsetY: e.offsetY,
      pageX: e.pageX,
      pageY: e.pageY,
      path: pathToSelector(e.path && e.path.length ? e.path[0] : null),
      relatedTarget: e.relatedTarget ? e.relatedTarget.outerHTML : null,
      returnValue: e.returnValue,
      screenX: e.screenX,
      screenY: e.screenY,
      shiftKey: e.shiftKey,
      sourceCapabilities: e.sourceCapabilities ? e.sourceCapabilities.toString() : null,
      target: e.target ? e.target.outerHTML : null,
      timeStamp: e.timeStamp,
      toElement: e.toElement ? e.toElement.outerHTML : null,
      type: e.type,
      view: e.view ? e.view.toString() : null,
      which: e.which,
      x: e.x,
      y: e.y
    };

    console.log(JSON.stringify(o, null, 2));
  }
};

// Create a mock event for this example
var evt = new MouseEvent("click", {
  bubbles: true,
  cancelable: true,
  view: window
});
var cb = document.getElementById("clicker");

// Add a click listener
cb.addEventListener("click", serializeEvent);

// Fire the event
cb.dispatchEvent(evt);
<div>
  <button id="clicker" /> JSONify my click!
</div>
Nim answered 16/2, 2017 at 20:59 Comment(1)
This should be the accepted answer. Yes, you need to customize the inner workings slightly based on your needs, but this is great overall! A few minor additions if you want to recreate the entire event: to find what event needs to be executed, do string operations on o.eventName, replace o.view with an object (usually window) and get your target element by parsing the o.target, e.g. see hereSanskritic
M
5

Improved version of the code given by Alexander Shutau, as this one handles multi level objects (ES6):

function stringify_object(object, depth=0, max_depth=2) {
    // change max_depth to see more levels, for a touch event, 2 is good
    if (depth > max_depth)
        return 'Object';

    const obj = {};
    for (let key in object) {
        let value = object[key];
        if (value instanceof Node)
            // specify which properties you want to see from the node
            value = {id: value.id};
        else if (value instanceof Window)
            value = 'Window';
        else if (value instanceof Object)
            value = stringify_object(value, depth+1, max_depth);

        obj[key] = value;
    }

    return depth? obj: JSON.stringify(obj);
}

Just call it like this:

stringify_object(event, 2);

For example, on a touchstart event, I'm getting this:

touchstart : {"isTrusted":true,"touches":{"0":{"identifier":0,"target":{"id":"screen"},"screenX":548,"screenY":281.5,"clientX":498.1817932128906,"clientY":185.90908813476562,"pageX":498.1817932128906,"pageY":185.90908813476562,"radiusX":29.77272605895996,"radiusY":27.954544067382812,"rotationAngle":0,"force":0.5},"length":1,"item":{}},"targetTouches":{"0":{"identifier":0,"target":{"id":"screen"},"screenX":548,"screenY":281.5,"clientX":498.1817932128906,"clientY":185.90908813476562,"pageX":498.1817932128906,"pageY":185.90908813476562,"radiusX":29.77272605895996,"radiusY":27.954544067382812,"rotationAngle":0,"force":0.5},"length":1,"item":{}},"changedTouches":{"0":{"identifier":0,"target":{"id":"screen"},"screenX":548,"screenY":281.5,"clientX":498.1817932128906,"clientY":185.90908813476562,"pageX":498.1817932128906,"pageY":185.90908813476562,"radiusX":29.77272605895996,"radiusY":27.954544067382812,"rotationAngle":0,"force":0.5},"length":1,"item":{}},"altKey":false,"metaKey":false,"ctrlKey":false,"shiftKey":false,"view":"Window","detail":0,"sourceCapabilities":{"firesTouchEvents":true},"which":0,"initUIEvent":{},"NONE":0,"CAPTURING_PHASE":1,"AT_TARGET":2,"BUBBLING_PHASE":3,"type":"touchstart","target":{"id":"screen"},"currentTarget":{"id":"screen"},"eventPhase":2,"bubbles":true,"cancelable":true,"defaultPrevented":false,"composed":true,"timeStamp":192516.7899999651,"srcElement":{"id":"screen"},"returnValue":true,"cancelBubble":false,"path":{"0":{"id":"screen"},"1":{"id":"back"},"2":{"id":""},"3":{"id":""},"4":{},"5":"Window"},"composedPath":{},"stopPropagation":{},"stopImmediatePropagation":{},"preventDefault":{},"initEvent":{}}
Menis answered 16/10, 2019 at 14:59 Comment(0)
C
1

Not sure if it helps, but I just stumbled upon this in the Angular JS documentation:

*Source: https://code.angularjs.org/1.5.5/docs/guide/expression#-event-

/*
 * return a copy of an object with only non-object keys
 * we need this to avoid circular references
 */
function simpleKeys (original) {
  return Object.keys(original).reduce(function (obj, key) {
    obj[key] = typeof original[key] === 'object' ? '{ ... }' : original[key];
    return obj;
  }, {});
}

Now you could do something like:

JSON.stringify(simpleKeys(eventObject));
Cervine answered 11/6, 2014 at 11:17 Comment(2)
When using this on a touchevent it only returns a single element {isTrusted: true}.Sickle
Same for all other MouseEvent'sGraptolite
L
-1

So, the issue is JSON.stringify seems to bail out as soon as it finds a circular reference. I was anyway not interested in the circularly referenced properties. The way I got the rest of them is

var str = "{"
for (var key in data) {
  if (JSON.stringify(data[key]) !== "") {
    str += key + ":" + data[key]) + ",";
  }
}
str += "}"

This will basically give you the rest of the properties. To avoid JS errors you can put if in try/catch.

Leery answered 12/2, 2018 at 10:13 Comment(0)
I
-4

Just use JSON.stringify(event) and event data should be converted to string.

Institutionalism answered 10/12, 2018 at 6:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.