PlainObjectSerializer.js 3.2 KB

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