One little used option of JSON.stringify(value, replacer, space) is the second argument replacer, which i've never seen set to anything except null.
As it turns out, that's for a very good reason. The replacer argument can either be provided as an Array or a Function, and both implementations have poor support for nested JSON.
Using either syntax results in considerably more bug than feature.
Saving Graceโ
However! in the footnotes of the documentation we find a clue to saving this feature from the ๐๏ธ of history:
The object in which the key was found is provided as the replacer's this context.
In other words, it calls (key, value) for the current JSON element, but also binds this to the parent element ๐ค
Get the JSON Path within replacerโ
With this in mind we can create a decorator function which tracks the JSON Path for each key as we traverse the object hierarchy:
function replacerWithPath (fn) {
const paths = new Map()
return function (key, value) {
let path = paths.get(this) || '$'
if (key) path += Array.isArray(this) ? `[${key}]` : `.${key}`
const v = fn(key, value, path)
if (v === Object(v)) paths.set(v, path)
return v
}
}
Now our replacer function will receive a third argument with the full path:
function replacer (key, value, path) {
console.log(path, key)
return value
}
Exampleโ
You can use it as you normally would with JSON.stringify():
const example = {
"isbn": "123-456-222",
"author": {
"lastname": "Doe",
"firstname": "Jane"
},
"editor": {
"lastname": "Smith",
"firstname": "Jane"
},
"title": "The Ultimate Database Study Guide",
"category": [
"Non-Fiction",
"Technology"
]
}
> JSON.stringify(example, replacerWithPath(replacer), 2)
$
$.isbn isbn
$.author author
$.author.lastname lastname
$.author.firstname firstname
$.editor editor
$.editor.lastname lastname
$.editor.firstname firstname
$.title title
$.category category
$.category[0] 0
$.category[1] 1
