Skip to main content

serializeWithFullSortedKeys

Serializes objects using flattened path notation with sorted keys for consistent output. Transforms nested object structures into a flat string representation where each property path is fully qualified using dot notation. All paths are sorted alphabetically to ensure deterministic serialization regardless of property definition order. Ideal for creating consistent object fingerprints, cache keys, and debugging output.

Signature

const serializeWithFullSortedKeys: (object: any) => string

Parameters

NameTypeDescription
object-Object to serialize with full path expansion

Returns

Pipe-delimited string of sorted 'fullpath:value' pairs

Examples

Basic nested object serialization

import { serializeWithFullSortedKeys } from '@winglet/common-utils';

// Simple nested structure
const config = {
database: {
host: 'localhost',
port: 5432
},
cache: {
enabled: true
}
};

console.log(serializeWithFullSortedKeys(config));
// 'cache.enabled:true|database.host:localhost|database.port:5432'

Complex nested structures

// Multi-level nesting with various data types
const appConfig = {
server: {
port: 3000,
ssl: {
enabled: true,
cert: {
path: '/etc/ssl/cert.pem',
passphrase: 'secret'
}
}
},
database: {
primary: {
host: 'db1.example.com',
credentials: {
username: 'admin',
password: 'password123'
}
},
replica: {
host: 'db2.example.com'
}
},
features: {
analytics: false,
logging: true
}
};

console.log(serializeWithFullSortedKeys(appConfig));
// 'database.primary.credentials.password:password123|database.primary.credentials.username:admin|database.primary.host:db1.example.com|database.replica.host:db2.example.com|features.analytics:false|features.logging:true|server.port:3000|server.ssl.cert.passphrase:secret|server.ssl.cert.path:/etc/ssl/cert.pem|server.ssl.enabled:true'

Configuration fingerprinting

// Create consistent configuration signatures
const createConfigSignature = (config: any) => {
const serialized = serializeWithFullSortedKeys(config);
// Could hash this for shorter fingerprints
return serialized;
};

const config1 = { api: { timeout: 5000 }, db: { host: 'localhost' } };
const config2 = { db: { host: 'localhost' }, api: { timeout: 5000 } }; // Different order

const sig1 = createConfigSignature(config1);
const sig2 = createConfigSignature(config2);

console.log(sig1 === sig2); // true (same signature despite different property order)
console.log(sig1); // 'api.timeout:5000|db.host:localhost'

Environment configuration processing

// Process environment-specific configurations
const environments = {
development: {
database: { debug: true, pool: { min: 1, max: 5 } },
logging: { level: 'debug', console: true }
},
production: {
database: { debug: false, pool: { min: 5, max: 20 } },
logging: { level: 'error', console: false }
}
};

Object.keys(environments).forEach(env => {
const signature = serializeWithFullSortedKeys(environments[env]);
console.log(`${env}: ${signature}`);
});
// development: database.debug:true|database.pool.max:5|database.pool.min:1|logging.console:true|logging.level:debug
// production: database.debug:false|database.pool.max:20|database.pool.min:5|logging.console:false|logging.level:error

API endpoint configuration

// Serialize API endpoint configurations
const apiEndpoints = {
users: {
base: '/api/v1/users',
methods: {
get: { auth: true, cache: 300 },
post: { auth: true, validation: true },
delete: { auth: true, admin: true }
},
rateLimit: {
requests: 100,
window: 3600
}
},
auth: {
base: '/api/v1/auth',
methods: {
login: { bruteforce: { attempts: 5, window: 900 } },
refresh: { auth: false }
}
}
};

const serialized = serializeWithFullSortedKeys(apiEndpoints);
console.log(serialized);
// 'auth.base:/api/v1/auth|auth.methods.login.bruteforce.attempts:5|auth.methods.login.bruteforce.window:900|auth.methods.refresh.auth:false|users.base:/api/v1/users|users.methods.delete.admin:true|users.methods.delete.auth:true|users.methods.get.auth:true|users.methods.get.cache:300|users.methods.post.auth:true|users.methods.post.validation:true|users.rateLimit.requests:100|users.rateLimit.window:3600'

Primitive and edge case handling

// Non-object values return string representation
console.log(serializeWithFullSortedKeys('hello')); // 'hello'
console.log(serializeWithFullSortedKeys(42)); // '42'
console.log(serializeWithFullSortedKeys(true)); // 'true'
console.log(serializeWithFullSortedKeys(null)); // 'null'
console.log(serializeWithFullSortedKeys(undefined)); // 'undefined'

// Empty objects
console.log(serializeWithFullSortedKeys({})); // ''

// Objects with special values
const specialValues = {
nullValue: null,
undefinedValue: undefined,
zeroValue: 0,
emptyString: '',
booleanFalse: false
};
console.log(serializeWithFullSortedKeys(specialValues));
// 'booleanFalse:false|emptyString:|nullValue:null|undefinedValue:undefined|zeroValue:0'

Database configuration comparison

// Compare database configurations
const compareConfigs = (config1: any, config2: any) => {
const sig1 = serializeWithFullSortedKeys(config1);
const sig2 = serializeWithFullSortedKeys(config2);
return sig1 === sig2;
};

const dbConfig1 = {
connection: {
host: 'localhost',
port: 5432,
ssl: { enabled: false }
},
pool: { min: 2, max: 10 }
};

const dbConfig2 = {
pool: { max: 10, min: 2 }, // Different order
connection: {
port: 5432, // Different order
host: 'localhost',
ssl: { enabled: false }
}
};

console.log(compareConfigs(dbConfig1, dbConfig2)); // true

Debugging nested object differences

// Debug configuration differences
const debugConfigDiff = (config1: any, config2: any) => {
const paths1 = serializeWithFullSortedKeys(config1).split('|');
const paths2 = serializeWithFullSortedKeys(config2).split('|');

const set1 = new Set(paths1);
const set2 = new Set(paths2);

const onlyIn1 = paths1.filter(path => !set2.has(path));
const onlyIn2 = paths2.filter(path => !set1.has(path));

return {
onlyInFirst: onlyIn1,
onlyInSecond: onlyIn2,
identical: onlyIn1.length === 0 && onlyIn2.length === 0
};
};

const oldConfig = { api: { timeout: 5000, retries: 3 } };
const newConfig = { api: { timeout: 8000, retries: 3, debug: true } };

const diff = debugConfigDiff(oldConfig, newConfig);
console.log(diff);
// {
// onlyInFirst: ['api.timeout:5000'],
// onlyInSecond: ['api.debug:true', 'api.timeout:8000'],
// identical: false
// }

Playground

import { serializeWithFullSortedKeys } from '@winglet/common-utils';

// Simple nested structure
const config = {
database: {
  host: 'localhost',
  port: 5432
},
cache: {
  enabled: true
}
};

console.log(serializeWithFullSortedKeys(config));
// 'cache.enabled:true|database.host:localhost|database.port:5432'

Notes

Serialization Strategy:

  • Path Expansion: Each nested property becomes a fully qualified path using dot notation
  • Alphabetical Sorting: All paths are sorted alphabetically for consistent output
  • Value Stringification: All values are converted to strings using String() conversion
  • Delimiter: Pipe character ('|') separates path-value pairs in the final output

Path Generation:

  • Root Level: Properties become direct paths (e.g., 'name', 'age')
  • Nested Objects: Properties become dot-separated paths (e.g., 'user.profile.name')
  • Deep Nesting: Supports unlimited nesting depth
  • Path Ordering: All paths sorted alphabetically regardless of definition order

Performance Characteristics:

  • Time Complexity: O(n log n) where n is total number of leaf properties (due to sorting)
  • Space Complexity: O(n) for path storage and result construction
  • Memory Usage: Efficient iterative traversal using internal stack
  • Scalability: Handles large nested objects efficiently

Value Handling:

  • Primitives: Direct string conversion (null, undefined, boolean, number)
  • Objects: Recursively expanded into individual path-value pairs
  • Non-Objects: Returned as string representation without expansion
  • Special Values: Maintains distinction between null, undefined, empty string, and false

Use Cases:

  • Configuration object fingerprinting and comparison
  • Cache key generation for nested data structures
  • Debugging and logging complex object states
  • API endpoint configuration serialization
  • Environment-specific configuration management
  • Object difference detection and analysis

Advantages over Alternatives:

  • Deterministic: Always produces same output for equivalent objects
  • Human Readable: Paths are intuitive and easy to understand
  • Sortable: Alphabetical ordering enables easy comparison and diffing
  • Flat Structure: No nested brackets or complex escaping needed
  • Debugging Friendly: Each path-value pair is independently readable

Limitations:

  • One-Way Transformation: Cannot be deserialized back to original object
  • No Array Indexing: Arrays are treated as objects with numeric keys
  • String Representation: All values become strings, losing type information
  • Path Collisions: Properties with dots in names may create ambiguous paths
  • Circular References: Will cause infinite loops (not handled)
  • Large Objects: Can produce very long strings for deeply nested structures