PlainObjectSerializer.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. */
  4. "use strict";
  5. /** @typedef {import("./ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */
  6. /** @typedef {import("./ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */
  7. /** @typedef {EXPECTED_FUNCTION} CacheAssoc */
  8. /**
  9. * Defines the shared type used by this module.
  10. * @template T
  11. * @typedef {WeakMap<CacheAssoc, ObjectStructure<T>>}
  12. */
  13. const cache = new WeakMap();
  14. /**
  15. * Represents ObjectStructure.
  16. * @template T
  17. */
  18. class ObjectStructure {
  19. constructor() {
  20. /** @type {undefined | keyof T[]} */
  21. this.keys = undefined;
  22. /** @type {undefined | Map<keyof T, ObjectStructure<T>>} */
  23. this.children = undefined;
  24. }
  25. /**
  26. * Returns keys.
  27. * @param {keyof T[]} keys keys
  28. * @returns {keyof T[]} keys
  29. */
  30. getKeys(keys) {
  31. if (this.keys === undefined) this.keys = keys;
  32. return this.keys;
  33. }
  34. /**
  35. * Returns object structure.
  36. * @param {keyof T} key key
  37. * @returns {ObjectStructure<T>} object structure
  38. */
  39. key(key) {
  40. if (this.children === undefined) this.children = new Map();
  41. const child = this.children.get(key);
  42. if (child !== undefined) return child;
  43. const newChild = new ObjectStructure();
  44. this.children.set(key, newChild);
  45. return newChild;
  46. }
  47. }
  48. /**
  49. * Returns keys.
  50. * @template T
  51. * @param {(keyof T)[]} keys keys
  52. * @param {CacheAssoc} cacheAssoc cache assoc fn
  53. * @returns {(keyof T)[]} keys
  54. */
  55. const getCachedKeys = (keys, cacheAssoc) => {
  56. let root = cache.get(cacheAssoc);
  57. if (root === undefined) {
  58. root = new ObjectStructure();
  59. cache.set(cacheAssoc, root);
  60. }
  61. let current = root;
  62. for (const key of keys) {
  63. current = current.key(key);
  64. }
  65. return current.getKeys(keys);
  66. };
  67. class PlainObjectSerializer {
  68. /**
  69. * Serializes this instance into the provided serializer context.
  70. * @template {object} T
  71. * @param {T} obj plain object
  72. * @param {ObjectSerializerContext} context context
  73. */
  74. serialize(obj, context) {
  75. const keys = /** @type {(keyof T)[]} */ (Object.keys(obj));
  76. if (keys.length > 128) {
  77. // Objects with so many keys are unlikely to share structure
  78. // with other objects
  79. context.write(keys);
  80. for (const key of keys) {
  81. context.write(obj[key]);
  82. }
  83. } else if (keys.length > 1) {
  84. context.write(getCachedKeys(keys, context.write));
  85. for (const key of keys) {
  86. context.write(obj[key]);
  87. }
  88. } else if (keys.length === 1) {
  89. const key = keys[0];
  90. context.write(key);
  91. context.write(obj[key]);
  92. } else {
  93. context.write(null);
  94. }
  95. }
  96. /**
  97. * Restores this instance from the provided deserializer context.
  98. * @template {object} T
  99. * @param {ObjectDeserializerContext} context context
  100. * @returns {T} plain object
  101. */
  102. deserialize(context) {
  103. const keys = context.read();
  104. const obj = /** @type {T} */ ({});
  105. if (Array.isArray(keys)) {
  106. for (const key of keys) {
  107. obj[/** @type {keyof T} */ (key)] = context.read();
  108. }
  109. } else if (keys !== null) {
  110. obj[/** @type {keyof T} */ (keys)] = context.read();
  111. }
  112. return obj;
  113. }
  114. }
  115. module.exports = PlainObjectSerializer;