Safe data serialization utilities with error handling for JSON conversion operations, providing robust string representation of JavaScript values.
Converts a JavaScript value to a JSON string with protection against thrown errors, providing safe serialization for any value including circular references.
/**
* Converts a JavaScript value to a JavaScript Object Notation (JSON) string with protection against thrown errors
* @param value - A JavaScript value, usually an object or array, to be converted
* @param replacer - The JSON.stringify() replacer argument
* @param space - Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read
* @returns The JSON string. If the operation fails, an error string value is returned (no exception thrown)
*/
function stringify(value: any, replacer?: any, space?: string | number): string;Usage Examples:
import { stringify } from "@hapi/hoek";
// Basic object stringification
const simpleObj = { name: 'Alice', age: 30, active: true };
const jsonString = stringify(simpleObj);
// Result: '{"name":"Alice","age":30,"active":true}'
// Pretty printing with indentation
const prettyJson = stringify(simpleObj, null, 2);
/* Result:
{
"name": "Alice",
"age": 30,
"active": true
}
*/
// Safe circular reference handling
const circularObj: any = { name: 'test' };
circularObj.self = circularObj; // Create circular reference
const safeCircular = stringify(circularObj);
// Result: '[Cannot display object: Converting circular structure to JSON]'
// No exception thrown, unlike JSON.stringify()
// Complex nested objects
const complexObj = {
users: [
{ id: 1, name: 'Alice', metadata: { active: true, roles: ['admin', 'user'] } },
{ id: 2, name: 'Bob', metadata: { active: false, roles: ['user'] } }
],
config: {
timeout: 5000,
retries: 3,
endpoints: ['api.example.com', 'backup.example.com']
}
};
const complexJson = stringify(complexObj, null, 2);
// Safely converts entire complex structure
// Custom replacer function
const objWithSensitiveData = {
username: 'alice',
password: 'secret123',
email: 'alice@example.com',
profile: { age: 30, city: 'New York' }
};
const safeReplacer = (key: string, value: any) => {
if (key === 'password') return '[REDACTED]';
return value;
};
const redactedJson = stringify(objWithSensitiveData, safeReplacer, 2);
// Result includes password as '[REDACTED]'
// Array replacer for key filtering
const arrayReplacer = ['username', 'email', 'profile'];
const filteredJson = stringify(objWithSensitiveData, arrayReplacer, 2);
// Only includes specified keys
// Error object stringification
const error = new Error('Something went wrong');
error.code = 'ERR_CUSTOM';
error.details = { step: 'validation', field: 'email' };
const errorJson = stringify(error);
// Safely converts error object (JSON.stringify would lose most properties)
// Date and special value handling
const specialValues = {
now: new Date(),
undefined: undefined,
null: null,
infinity: Infinity,
nan: NaN,
bigint: 123n
};
const specialJson = stringify(specialValues, null, 2);
// Handles all special JavaScript values safely
// Function and symbol handling
const objWithFunctions = {
data: 'value',
method: function() { return 'result'; },
symbol: Symbol('test'),
arrow: () => 'arrow result'
};
const funcJson = stringify(objWithFunctions);
// Functions and symbols are handled according to JSON.stringify rules
// Safe logging utility
function safeLog(label: string, value: any) {
console.log(`${label}:`, stringify(value, null, 2));
}
// Usage in error handling
try {
riskyOperation();
} catch (error) {
console.error('Operation failed:', stringify(error));
// Safe even if error object has circular references or other issues
}
// API response debugging
async function debugAPIResponse(url: string) {
try {
const response = await fetch(url);
const data = await response.json();
console.log('Response data:', stringify(data, null, 2));
return data;
} catch (error) {
console.error('API Error:', stringify({
url,
error: error.message,
stack: error.stack,
timestamp: new Date().toISOString()
}, null, 2));
throw error;
}
}
// Configuration serialization
function serializeConfig(config: any): string {
return stringify(config, (key, value) => {
// Redact sensitive configuration values
if (key.toLowerCase().includes('password') ||
key.toLowerCase().includes('secret') ||
key.toLowerCase().includes('token')) {
return '[REDACTED]';
}
return value;
}, 2);
}
// Safe state serialization for debugging
class StatefulComponent {
private state: any;
getStateSnapshot(): string {
return stringify(this.state, null, 2);
}
debugState(): void {
console.log('Current state:', this.getStateSnapshot());
}
}
// Large object handling
function handleLargeObject(largeObj: any) {
try {
// Try normal JSON.stringify first for performance
return JSON.stringify(largeObj);
} catch (error) {
// Fall back to safe stringify
console.warn('Large object has issues, using safe stringify');
return stringify(largeObj);
}
}
// Custom space formatting
const customSpaceObj = { a: 1, b: { c: 2, d: 3 } };
// Using string space
const tabFormatted = stringify(customSpaceObj, null, '\t');
// Using number space
const fourSpaceFormatted = stringify(customSpaceObj, null, 4);
// No formatting
const minified = stringify(customSpaceObj);
// Testing and development utilities
function createTestSnapshot(testData: any): string {
return stringify(testData, (key, value) => {
// Normalize timestamps for consistent testing
if (key.includes('timestamp') || key.includes('time')) {
return '[TIMESTAMP]';
}
// Normalize IDs
if (key === 'id' && typeof value === 'string') {
return '[ID]';
}
return value;
}, 2);
}Important Notes:
stringify never throws exceptions, unlike JSON.stringify() which can throw on circular referencesJSON.stringify(): value, replacer, and space'[Cannot display object: <error description>]'JSON.stringify() for untrusted or complex data structures