Replacer in below code write on console current processed field name
replacerWithPath in snippet determine path using this (thanks @Andreas for this tip ), field and value and some historical data stored during execution (and this solution support arrays)
JSON.stringify(c, replacerWithPath(function(field,value,path) {
console.log(path,'=',value);
return value;
}));
function replacerWithPath(replacer) {
let m = new Map();
return function(field, value) {
let path= m.get(this) + (Array.isArray(this) ? `[${field}]` : '.' + field);
if (value===Object(value)) m.set(value, path);
return replacer.call(this, field, value, path.replace(/undefined\.\.?/,''))
}
}
// Explanation fo replacerWithPath decorator:
// > 'this' inside 'return function' point to field parent object
// (JSON.stringify execute replacer like that)
// > 'path' contains path to current field based on parent ('this') path
// previously saved in Map
// > during path generation we check is parent ('this') array or object
// and chose: "[field]" or ".field"
// > in Map we store current 'path' for given 'field' only if it
// is obj or arr in this way path to each parent is stored in Map.
// We don't need to store path to simple types (number, bool, str,...)
// because they never will have children
// > value===Object(value) -> is true if value is object or array
// (more: https://stackoverflow.com/a/22482737/860099)
// > path for main object parent is set as 'undefined.' so we cut out that
// prefix at the end ad call replacer with that path
// ----------------
// TEST
// ----------------
let a = { a1: 1, a2: 1 };
let b = { b1: 2, b2: [1, a] };
let c = { c1: 3, c2: b };
let s = JSON.stringify(c, replacerWithPath(function(field, value, path) {
// "this" has same value as in replacer without decoration
console.log(path);
return value;
}));
BONUS: I use this approach to stringify objects with circular references here