PlainObjectSerializer.js 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  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. * @template T
  10. * @typedef {WeakMap<CacheAssoc, ObjectStructure<T>>}
  11. */
  12. const cache = new WeakMap();
  13. /**
  14. * @template T
  15. */
  16. class ObjectStructure {
  17. constructor() {
  18. /** @type {undefined | keyof T[]} */
  19. this.keys = undefined;
  20. /** @type {undefined | Map<keyof T, ObjectStructure<T>>} */
  21. this.children = undefined;
  22. }
  23. /**
  24. * @param {keyof T[]} keys keys
  25. * @returns {keyof T[]} keys
  26. */
  27. getKeys(keys) {
  28. if (this.keys === undefined) this.keys = keys;
  29. return this.keys;
  30. }
  31. /**
  32. * @param {keyof T} key key
  33. * @returns {ObjectStructure<T>} object structure
  34. */
  35. key(key) {
  36. if (this.children === undefined) this.children = new Map();
  37. const child = this.children.get(key);
  38. if (child !== undefined) return child;
  39. const newChild = new ObjectStructure();
  40. this.children.set(key, newChild);
  41. return newChild;
  42. }
  43. }
  44. /**
  45. * @template T
  46. * @param {(keyof T)[]} keys keys
  47. * @param {CacheAssoc} cacheAssoc cache assoc fn
  48. * @returns {(keyof T)[]} keys
  49. */
  50. const getCachedKeys = (keys, cacheAssoc) => {
  51. let root = cache.get(cacheAssoc);
  52. if (root === undefined) {
  53. root = new ObjectStructure();
  54. cache.set(cacheAssoc, root);
  55. }
  56. let current = root;
  57. for (const key of keys) {
  58. current = current.key(key);
  59. }
  60. return current.getKeys(keys);
  61. };
  62. class PlainObjectSerializer {
  63. /**
  64. * @template {object} T
  65. * @param {T} obj plain object
  66. * @param {ObjectSerializerContext} context context
  67. */
  68. serialize(obj, context) {
  69. const keys = /** @type {(keyof T)[]} */ (Object.keys(obj));
  70. if (keys.length > 128) {
  71. // Objects with so many keys are unlikely to share structure
  72. // with other objects
  73. context.write(keys);
  74. for (const key of keys) {
  75. context.write(obj[key]);
  76. }
  77. } else if (keys.length > 1) {
  78. context.write(getCachedKeys(keys, context.write));
  79. for (const key of keys) {
  80. context.write(obj[key]);
  81. }
  82. } else if (keys.length === 1) {
  83. const key = keys[0];
  84. context.write(key);
  85. context.write(obj[key]);
  86. } else {
  87. context.write(null);
  88. }
  89. }
  90. /**
  91. * @template {object} T
  92. * @param {ObjectDeserializerContext} context context
  93. * @returns {T} plain object
  94. */
  95. deserialize(context) {
  96. const keys = context.read();
  97. const obj = /** @type {T} */ ({});
  98. if (Array.isArray(keys)) {
  99. for (const key of keys) {
  100. obj[/** @type {keyof T} */ (key)] = context.read();
  101. }
  102. } else if (keys !== null) {
  103. obj[/** @type {keyof T} */ (keys)] = context.read();
  104. }
  105. return obj;
  106. }
  107. }
  108. module.exports = PlainObjectSerializer;