serialization.js 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. /**
  2. * @fileoverview Serialization utils.
  3. * @author Bryan Mishkin
  4. */
  5. "use strict";
  6. /**
  7. * Check if a value is a primitive or plain object created by the Object constructor.
  8. * @param {any} val the value to check
  9. * @returns {boolean} true if so
  10. * @private
  11. */
  12. function isSerializablePrimitiveOrPlainObject(val) {
  13. return (
  14. val === null ||
  15. typeof val === "string" ||
  16. typeof val === "boolean" ||
  17. typeof val === "number" ||
  18. (typeof val === "object" && val.constructor === Object) ||
  19. Array.isArray(val)
  20. );
  21. }
  22. /**
  23. * Check if a value is serializable.
  24. * Functions or objects like RegExp cannot be serialized by JSON.stringify().
  25. * Inspired by: https://stackoverflow.com/questions/30579940/reliable-way-to-check-if-objects-is-serializable-in-javascript
  26. * @param {any} val The value
  27. * @param {Set<Object>} seenObjects Objects already seen in this path from the root object.
  28. * @returns {boolean} `true` if the value is serializable
  29. */
  30. function isSerializable(val, seenObjects = new Set()) {
  31. if (!isSerializablePrimitiveOrPlainObject(val)) {
  32. return false;
  33. }
  34. if (typeof val === "object" && val !== null) {
  35. if (seenObjects.has(val)) {
  36. /*
  37. * Since this is a depth-first traversal, encountering
  38. * the same object again means there is a circular reference.
  39. * Objects with circular references are not serializable.
  40. */
  41. return false;
  42. }
  43. for (const property in val) {
  44. if (Object.hasOwn(val, property)) {
  45. if (!isSerializablePrimitiveOrPlainObject(val[property])) {
  46. return false;
  47. }
  48. if (
  49. typeof val[property] === "object" &&
  50. val[property] !== null
  51. ) {
  52. if (
  53. /*
  54. * We're creating a new Set of seen objects because we want to
  55. * ensure that `val` doesn't appear again in this path, but it can appear
  56. * in other paths. This allows for reusing objects in the graph, as long as
  57. * there are no cycles.
  58. */
  59. !isSerializable(
  60. val[property],
  61. new Set([...seenObjects, val]),
  62. )
  63. ) {
  64. return false;
  65. }
  66. }
  67. }
  68. }
  69. }
  70. return true;
  71. }
  72. module.exports = {
  73. isSerializable,
  74. };