PlainObjectSerializer.js 2.7 KB

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