stringify-chunked.cjs 5.0 KB

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