admin管理员组文章数量:1240597
Replacer in below code write on console current processed field name
let a = { a1: 1, a2:1 }
let b = { b1: 2, b2: [1,a] }
let c = { c1: 3, c2: b }
let s = JSON.stringify(c, function (field,value) {
console.log(field); // full path... ???
return value;
});
Replacer in below code write on console current processed field name
let a = { a1: 1, a2:1 }
let b = { b1: 2, b2: [1,a] }
let c = { c1: 3, c2: b }
let s = JSON.stringify(c, function (field,value) {
console.log(field); // full path... ???
return value;
});
However I would like to get full "path" to field (not only its name) inside replacer function - something like this
c1
c2
c2.b1
c2.b2
c2.b2[0]
c2.b2[1]
c2.b2[1].a1
c2.b2[1].a2
How to do it?
Share Improve this question edited May 9, 2020 at 19:00 Kamil Kiełczewski asked May 8, 2020 at 14:14 Kamil KiełczewskiKamil Kiełczewski 92.5k34 gold badges395 silver badges370 bronze badges 2-
"As a function, it takes two parameters: the key and the value being stringified. The object in which the key was found is provided as the replacer's this parameter." - You will have to find yourself a way how to generate the path from the key and
this
. – Andreas Commented May 8, 2020 at 14:28 - stackoverflow./questions/49125398/… – Slai Commented May 8, 2020 at 14:55
5 Answers
Reset to default 7Decorator
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./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
You can use custom walk function inside your replacer
. Here's an example using a generator walk function:
const testObject = {a: 1, b: {a: 11, b: {a: 111, b: 222, c: 333}}, c: 3};
function* walk(object, path = []) {
for (const [key, value] of Object.entries(object)) {
yield path.concat(key);
if (typeof value === 'object') yield* walk(value, path.concat(key));
}
}
const keyGenerator = walk(testObject);
JSON.stringify(testObject, (key, value) => {
const fullKey = key === '' ? [] : keyGenerator.next().value;
// fullKey contains an array with entire key path
console.log(fullKey, value);
return value;
});
Console output:
fullKey | value
-------------------|------------------------------------------------------------
[] | {"a":1,"b":{"a":11,"b":{"a":111,"b":222,"c":333}},"c":3}
["a"] | 1
["b"] | {"a":11,"b":{"a":111,"b":222,"c":333}}
["b", "a"] | 11
["b", "b"] | {"a":111,"b":222,"c":333}
["b", "b", "a"] | 111
["b", "b", "b"] | 222
["b", "b", "c"] | 333
["c"] | 3
This works, assuming the algorithm in replacer
is depth-first and consistent across all browsers.
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 ing 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;
}
There's just not enough information available in the replacer. These two objects have different shapes but produce the same sequence of calls:
let c1 = { c1: 3, c2: 2 };
let c2 = { c1: { c2: 3 } };
const replacer = function (field, value) {
console.log(field); // full path... ???
return value;
};
JSON.stringify(c1, replacer);
// logs c1, c2
JSON.stringify(c2, replacer);
// logs c1, c2
You'll have to write something yourself.
Something like that. You need to adjust it for arrays. I think that you can do it yourself. The idea is clear.
let a = { a1: 1, a2:1 }
let b = { b1: 2, b2: [1,a] }
let c = { c1: 3, c2: b }
function iterate(obj, path = '') {
for (var property in obj) {
if (obj.hasOwnProperty(property)) {
if (typeof obj[property] == "object") {
iterate(obj[property], path + property + '.');
}
else {
console.log(path + property);
}
}
}
}
iterate(c)
本文标签: javascriptJSONstringify replacerhow to get full pathStack Overflow
版权声明:本文标题:javascript - JSON.stringify replacer - how to get full path - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1740064576a2222766.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论