2
0

safe-stringify.ts 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. /**
  2. * Safe JSON stringification utility that handles circular references,
  3. * Error objects, and other special types.
  4. */
  5. /**
  6. * Serialize an error object to a plain object with all relevant properties
  7. */
  8. function serializeError(error: Error): Record<string, unknown> {
  9. return {
  10. message: error.message,
  11. name: error.name,
  12. stack: error.stack,
  13. // Include any additional enumerable properties
  14. ...Object.getOwnPropertyNames(error)
  15. .filter((key) => key !== "message" && key !== "name" && key !== "stack")
  16. .reduce(
  17. (acc, key) => {
  18. acc[key] = (error as unknown as Record<string, unknown>)[key]
  19. return acc
  20. },
  21. {} as Record<string, unknown>,
  22. ),
  23. }
  24. }
  25. /**
  26. * Safe stringify that handles circular references, Error objects, Dates, RegExp, etc.
  27. * Returns a serializable version of the object.
  28. */
  29. export function safeStringify(obj: unknown, seen = new WeakSet()): unknown {
  30. // Handle primitives
  31. if (obj === null || typeof obj !== "object") {
  32. return obj
  33. }
  34. // Handle circular references
  35. if (seen.has(obj as object)) {
  36. return "[Circular]"
  37. }
  38. // Handle Error objects
  39. if (obj instanceof Error) {
  40. return serializeError(obj)
  41. }
  42. // Handle arrays
  43. if (Array.isArray(obj)) {
  44. seen.add(obj)
  45. const result = obj.map((item) => safeStringify(item, seen))
  46. seen.delete(obj)
  47. return result
  48. }
  49. // Handle Date objects
  50. if (obj instanceof Date) {
  51. return obj.toISOString()
  52. }
  53. // Handle RegExp objects
  54. if (obj instanceof RegExp) {
  55. return obj.toString()
  56. }
  57. // Handle plain objects
  58. seen.add(obj)
  59. const result: Record<string, unknown> = {}
  60. for (const [key, value] of Object.entries(obj)) {
  61. try {
  62. result[key] = safeStringify(value, seen)
  63. } catch (_error) {
  64. // If serialization fails for a property, mark it
  65. result[key] = "[Serialization Error]"
  66. }
  67. }
  68. seen.delete(obj)
  69. return result
  70. }
  71. /**
  72. * Convert an argument to a string representation, handling circular references
  73. * and special types. This is specifically designed for console logging.
  74. */
  75. export function argToString(arg: unknown): string {
  76. if (typeof arg === "string") {
  77. return arg
  78. }
  79. try {
  80. const safe = safeStringify(arg)
  81. return JSON.stringify(safe)
  82. } catch (_error) {
  83. // Fallback if even safe stringify fails
  84. return "[Unable to stringify]"
  85. }
  86. }
  87. /**
  88. * Convert multiple arguments to a single string message, handling circular references
  89. */
  90. export function argsToMessage(args: unknown[]): string {
  91. return args.map(argToString).join(" ")
  92. }