Based on the other answers I have this function which adds a third path
argument to the call of replacer
:
function replacerWithPath(replacer) {
const m = new Map();
return function (field, value) {
const pathname = m.get(this);
let path;
if (pathname) {
const suffix = Array.isArray(this) ? `[${field}]` : `.${field}`;
path = pathname + suffix;
} else {
path = field;
}
if (value === Object(value)) {
m.set(value, path);
}
return replacer.call(this, field, value, path);
}
}
// Usage
function replacer(name, data, path) {
// ...
}
const dataStr = JSON.stringify(data, replacerWithPath(replacer));
BONUS:
I also created this function to iterate through an object in depth and to be able to use the replace function like with JSON.stringify
.
The third argument to true
will keep undefined
values and empty objects.
It can be handy to modify and ignore values while iterating through an object, it returns the new object (without stringify).
function walkWith(obj, fn, preserveUndefined) {
const walk = objPart => {
if (objPart === undefined) {
return;
}
let result;
// TODO other types than object
for (const key in objPart) {
const val = objPart[key];
let modified;
if (val === Object(val)) {
modified = walk(fn.call(objPart, key, val));
} else {
modified = fn.call(objPart, key, val);
}
if (preserveUndefined || modified !== undefined) {
if (result === undefined) {
result = {};
}
result[key] = modified;
}
}
return result;
};
return walk(fn.call({ '': obj }, '', obj));
}
BONUS 2:
I use it to transform a data
object coming from a form submission and containing files / arrays of files in mixed multipart, files + JSON.
function toMixedMultipart(data, bodyKey = 'data', form = new FormData()) {
const replacer = (name, value, path) => {
// Simple Blob
if (value instanceof Blob) {
form.append(path, value);
return undefined;
}
// Array of Blobs
if (Array.isArray(value) && value.every(v => (v instanceof Blob))) {
value.forEach((v, i) => {
form.append(`${path}[${i}]`, v);
});
return undefined;
}
return value;
};
const dataStr = JSON.stringify(data, replacerWithPath(replacer));
const dataBlob = new Blob([dataStr], { type: 'application/json' });
form.append(bodyKey, dataBlob);
return form;
}