stringify-chunked.js 5.0 KB

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