stringify-chunked.cjs 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. 'use strict';
  2. const utils = require('./utils.cjs');
  3. function encodeString(value) {
  4. if (/[^\x20\x21\x23-\x5B\x5D-\uD799]/.test(value)) { // [^\x20-\uD799]|[\x22\x5c]
  5. return JSON.stringify(value);
  6. }
  7. return '"' + value + '"';
  8. }
  9. function* stringifyChunked(value, ...args) {
  10. const { replacer, getKeys, space, ...options } = utils.normalizeStringifyOptions(...args);
  11. const highWaterMark = Number(options.highWaterMark) || 0x4000; // 16kb by default
  12. const keyStrings = new Map();
  13. const stack = [];
  14. const rootValue = { '': value };
  15. let prevState = null;
  16. let state = () => printEntry('', value);
  17. let stateValue = rootValue;
  18. let stateEmpty = true;
  19. let stateKeys = [''];
  20. let stateIndex = 0;
  21. let buffer = '';
  22. while (true) {
  23. state();
  24. if (buffer.length >= highWaterMark || prevState === null) {
  25. // flush buffer
  26. yield buffer;
  27. buffer = '';
  28. if (prevState === null) {
  29. break;
  30. }
  31. }
  32. }
  33. function printObject() {
  34. if (stateIndex === 0) {
  35. stateKeys = getKeys(stateValue);
  36. buffer += '{';
  37. }
  38. // when no keys left
  39. if (stateIndex === stateKeys.length) {
  40. buffer += space && !stateEmpty
  41. ? `\n${space.repeat(stack.length - 1)}}`
  42. : '}';
  43. popState();
  44. return;
  45. }
  46. const key = stateKeys[stateIndex++];
  47. printEntry(key, stateValue[key]);
  48. }
  49. function printArray() {
  50. if (stateIndex === 0) {
  51. buffer += '[';
  52. }
  53. if (stateIndex === stateValue.length) {
  54. buffer += space && !stateEmpty
  55. ? `\n${space.repeat(stack.length - 1)}]`
  56. : ']';
  57. popState();
  58. return;
  59. }
  60. printEntry(stateIndex, stateValue[stateIndex++]);
  61. }
  62. function printEntryPrelude(key) {
  63. if (stateEmpty) {
  64. stateEmpty = false;
  65. } else {
  66. buffer += ',';
  67. }
  68. if (space && prevState !== null) {
  69. buffer += `\n${space.repeat(stack.length)}`;
  70. }
  71. if (state === printObject) {
  72. let keyString = keyStrings.get(key);
  73. if (keyString === undefined) {
  74. keyStrings.set(key, keyString = encodeString(key) + (space ? ': ' : ':'));
  75. }
  76. buffer += keyString;
  77. }
  78. }
  79. function printEntry(key, value) {
  80. value = utils.replaceValue(stateValue, key, value, replacer);
  81. if (value === null || typeof value !== 'object') {
  82. // primitive
  83. if (state !== printObject || value !== undefined) {
  84. printEntryPrelude(key);
  85. pushPrimitive(value);
  86. }
  87. } else {
  88. // If the visited set does not change after adding a value, then it is already in the set
  89. if (stack.includes(value)) {
  90. throw new TypeError('Converting circular structure to JSON');
  91. }
  92. printEntryPrelude(key);
  93. stack.push(value);
  94. pushState();
  95. state = Array.isArray(value) ? printArray : printObject;
  96. stateValue = value;
  97. stateEmpty = true;
  98. stateIndex = 0;
  99. }
  100. }
  101. function pushPrimitive(value) {
  102. switch (typeof value) {
  103. case 'string':
  104. buffer += encodeString(value);
  105. break;
  106. case 'number':
  107. buffer += Number.isFinite(value) ? String(value) : 'null';
  108. break;
  109. case 'boolean':
  110. buffer += value ? 'true' : 'false';
  111. break;
  112. case 'undefined':
  113. case 'object': // typeof null === 'object'
  114. buffer += 'null';
  115. break;
  116. default:
  117. throw new TypeError(`Do not know how to serialize a ${value.constructor?.name || typeof value}`);
  118. }
  119. }
  120. function pushState() {
  121. prevState = {
  122. keys: stateKeys,
  123. index: stateIndex,
  124. prev: prevState
  125. };
  126. }
  127. function popState() {
  128. stack.pop();
  129. const value = stack.length > 0 ? stack[stack.length - 1] : rootValue;
  130. // restore state
  131. state = Array.isArray(value) ? printArray : printObject;
  132. stateValue = value;
  133. stateEmpty = false;
  134. stateKeys = prevState.keys;
  135. stateIndex = prevState.index;
  136. // pop state
  137. prevState = prevState.prev;
  138. }
  139. }
  140. exports.stringifyChunked = stringifyChunked;