index.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. module.exports = stringify
  2. stringify.default = stringify
  3. stringify.stable = deterministicStringify
  4. stringify.stableStringify = deterministicStringify
  5. var LIMIT_REPLACE_NODE = '[...]'
  6. var CIRCULAR_REPLACE_NODE = '[Circular]'
  7. var arr = []
  8. var replacerStack = []
  9. function defaultOptions () {
  10. return {
  11. depthLimit: Number.MAX_SAFE_INTEGER,
  12. edgesLimit: Number.MAX_SAFE_INTEGER
  13. }
  14. }
  15. // Regular stringify
  16. function stringify (obj, replacer, spacer, options) {
  17. if (typeof options === 'undefined') {
  18. options = defaultOptions()
  19. }
  20. decirc(obj, '', 0, [], undefined, 0, options)
  21. var res
  22. try {
  23. if (replacerStack.length === 0) {
  24. res = JSON.stringify(obj, replacer, spacer)
  25. } else {
  26. res = JSON.stringify(obj, replaceGetterValues(replacer), spacer)
  27. }
  28. } catch (_) {
  29. return JSON.stringify('[unable to serialize, circular reference is too complex to analyze]')
  30. } finally {
  31. while (arr.length !== 0) {
  32. var part = arr.pop()
  33. if (part.length === 4) {
  34. Object.defineProperty(part[0], part[1], part[3])
  35. } else {
  36. part[0][part[1]] = part[2]
  37. }
  38. }
  39. }
  40. return res
  41. }
  42. function setReplace (replace, val, k, parent) {
  43. var propertyDescriptor = Object.getOwnPropertyDescriptor(parent, k)
  44. if (propertyDescriptor.get !== undefined) {
  45. if (propertyDescriptor.configurable) {
  46. Object.defineProperty(parent, k, { value: replace })
  47. arr.push([parent, k, val, propertyDescriptor])
  48. } else {
  49. replacerStack.push([val, k, replace])
  50. }
  51. } else {
  52. parent[k] = replace
  53. arr.push([parent, k, val])
  54. }
  55. }
  56. function decirc (val, k, edgeIndex, stack, parent, depth, options) {
  57. depth += 1
  58. var i
  59. if (typeof val === 'object' && val !== null) {
  60. for (i = 0; i < stack.length; i++) {
  61. if (stack[i] === val) {
  62. setReplace(CIRCULAR_REPLACE_NODE, val, k, parent)
  63. return
  64. }
  65. }
  66. if (
  67. typeof options.depthLimit !== 'undefined' &&
  68. depth > options.depthLimit
  69. ) {
  70. setReplace(LIMIT_REPLACE_NODE, val, k, parent)
  71. return
  72. }
  73. if (
  74. typeof options.edgesLimit !== 'undefined' &&
  75. edgeIndex + 1 > options.edgesLimit
  76. ) {
  77. setReplace(LIMIT_REPLACE_NODE, val, k, parent)
  78. return
  79. }
  80. stack.push(val)
  81. // Optimize for Arrays. Big arrays could kill the performance otherwise!
  82. if (Array.isArray(val)) {
  83. for (i = 0; i < val.length; i++) {
  84. decirc(val[i], i, i, stack, val, depth, options)
  85. }
  86. } else {
  87. var keys = Object.keys(val)
  88. for (i = 0; i < keys.length; i++) {
  89. var key = keys[i]
  90. decirc(val[key], key, i, stack, val, depth, options)
  91. }
  92. }
  93. stack.pop()
  94. }
  95. }
  96. // Stable-stringify
  97. function compareFunction (a, b) {
  98. if (a < b) {
  99. return -1
  100. }
  101. if (a > b) {
  102. return 1
  103. }
  104. return 0
  105. }
  106. function deterministicStringify (obj, replacer, spacer, options) {
  107. if (typeof options === 'undefined') {
  108. options = defaultOptions()
  109. }
  110. var tmp = deterministicDecirc(obj, '', 0, [], undefined, 0, options) || obj
  111. var res
  112. try {
  113. if (replacerStack.length === 0) {
  114. res = JSON.stringify(tmp, replacer, spacer)
  115. } else {
  116. res = JSON.stringify(tmp, replaceGetterValues(replacer), spacer)
  117. }
  118. } catch (_) {
  119. return JSON.stringify('[unable to serialize, circular reference is too complex to analyze]')
  120. } finally {
  121. // Ensure that we restore the object as it was.
  122. while (arr.length !== 0) {
  123. var part = arr.pop()
  124. if (part.length === 4) {
  125. Object.defineProperty(part[0], part[1], part[3])
  126. } else {
  127. part[0][part[1]] = part[2]
  128. }
  129. }
  130. }
  131. return res
  132. }
  133. function deterministicDecirc (val, k, edgeIndex, stack, parent, depth, options) {
  134. depth += 1
  135. var i
  136. if (typeof val === 'object' && val !== null) {
  137. for (i = 0; i < stack.length; i++) {
  138. if (stack[i] === val) {
  139. setReplace(CIRCULAR_REPLACE_NODE, val, k, parent)
  140. return
  141. }
  142. }
  143. try {
  144. if (typeof val.toJSON === 'function') {
  145. return
  146. }
  147. } catch (_) {
  148. return
  149. }
  150. if (
  151. typeof options.depthLimit !== 'undefined' &&
  152. depth > options.depthLimit
  153. ) {
  154. setReplace(LIMIT_REPLACE_NODE, val, k, parent)
  155. return
  156. }
  157. if (
  158. typeof options.edgesLimit !== 'undefined' &&
  159. edgeIndex + 1 > options.edgesLimit
  160. ) {
  161. setReplace(LIMIT_REPLACE_NODE, val, k, parent)
  162. return
  163. }
  164. stack.push(val)
  165. // Optimize for Arrays. Big arrays could kill the performance otherwise!
  166. if (Array.isArray(val)) {
  167. for (i = 0; i < val.length; i++) {
  168. deterministicDecirc(val[i], i, i, stack, val, depth, options)
  169. }
  170. } else {
  171. // Create a temporary object in the required way
  172. var tmp = {}
  173. var keys = Object.keys(val).sort(compareFunction)
  174. for (i = 0; i < keys.length; i++) {
  175. var key = keys[i]
  176. deterministicDecirc(val[key], key, i, stack, val, depth, options)
  177. tmp[key] = val[key]
  178. }
  179. if (typeof parent !== 'undefined') {
  180. arr.push([parent, k, val])
  181. parent[k] = tmp
  182. } else {
  183. return tmp
  184. }
  185. }
  186. stack.pop()
  187. }
  188. }
  189. // wraps replacer function to handle values we couldn't replace
  190. // and mark them as replaced value
  191. function replaceGetterValues (replacer) {
  192. replacer =
  193. typeof replacer !== 'undefined'
  194. ? replacer
  195. : function (k, v) {
  196. return v
  197. }
  198. return function (key, val) {
  199. if (replacerStack.length > 0) {
  200. for (var i = 0; i < replacerStack.length; i++) {
  201. var part = replacerStack[i]
  202. if (part[1] === key && part[0] === val) {
  203. val = part[2]
  204. replacerStack.splice(i, 1)
  205. break
  206. }
  207. }
  208. }
  209. return replacer.call(this, key, val)
  210. }
  211. }