Users can download JSON files from our application, which I want to prettify for easier debugging than if all is in one line. However this increases the file size by nearly 40% even if the indentation is just a single tab or space.
As a compromise, I want to exclude all values with e.g. the key "large" from prettification, like this:
{
"small":
{
"a": 1,
"b": 2,
"large": {"a":1,"b":2,"c":3,"d":"the \"large\" object should not be prettified"}
},
"large": {"a":1,"b":2,"c":3,"d":"the \"large\" object should not be prettified"}
}
I tried to solve this using the replacer parameter of JSON.stringify:
JSON.stringify(data,(key,value)=>key==="large"?JSON.stringify(data):value,'\t');
However the values for the "large" keys ends up escaped:
data =
{
"small":
{
"a": 1,
"b": [2,3,4,5],
"large": {"myarray":[1,2,3,4],"b":2,"c":3,"d":"the \"large\" object should not be prettified"}
},
"large": {"a":1,"b":2,"c":3,"d":"the \"large\" object should not be prettified"}
}
const json = JSON.stringify(data,(key,value)=>key==="large"?JSON.stringify(data):value,'\t');
document.getElementById("json").innerText = json;
<html>
<body>
<pre id="json">
</pre>
</body>
</html>
How can I prevent this escaping from happening or otherwise partially prettify JSON in JavaScript?
Every time you call JSON.stringify
on an object (or part of an object) more than once, you're going to end up with escaped speech marks. In this case, after the inner JSON.stringify
function has been applied inside the replacer function, there is no way to tell the outer JSON.stringify
function not to treat that instance as it would any other string.
So, I think if your goal is to 'partially' stringify an object, you need to write a function that implements that. For this problem, I would suggest something like this:
data = {
"small": {
"a": 1,
"b": [2, 3, 4, 5],
"c": [2, {"test":"example"}, [4,4], 5],
"large": {
"myarray": [1, 2, 3, 4],
"b": 2,
"c": 3,
"d": "the \"large\" object should not be prettified"
}
},
"large": {
"a": 1,
"b": 2,
"c": 3,
"d": "the \"large\" object should not be prettified"
}
}
function print(obj, exclude, space) {
let recur = (obj, spacing, inarray) => {
let txt = '';
if (inarray) {
if (Array.isArray(obj)) {
txt += '[';
for(let i=0;i<obj.length;i++) {
txt += recur(obj[i], spacing + space, true);
};
txt = txt.substr(0, Math.max(1,txt.length - 2)) + ']';
} else if (typeof obj === 'object' && obj !== null) {
txt += '{' + recur(obj, spacing + space, false) + '\n' + spacing + '}';
} else if (typeof obj === 'string') {
txt += obj.replaceAll(/\"/g, '\\"') + '"';
} else {
txt += obj;
};
return txt + ', ';
} else {
for (let key of Object.keys(obj)) {
if (exclude.indexOf(key) !== -1) {
txt += '\n' + spacing + '"' + key + '": ' + JSON.stringify(obj[key]);
} else if (Array.isArray(obj[key])) {
txt += '\n' + spacing + '"' + key + '": [';
for(let i=0;i<obj[key].length;i++) {
txt += recur(obj[key][i], spacing + space, true);
};
txt = txt.substr(0, Math.max(1,txt.length - 2)) + ']';
} else if (typeof obj[key] === 'object' && obj[key] !== null) {
txt += '\n' + spacing + '"' + key + '": {' + recur(obj[key], spacing + space, false) + '\n' + spacing + '}';
} else if (typeof obj[key] === 'string') {
txt += '\n' + spacing + '"' + key + '": "' + obj[key].replaceAll(/\"/g, '\\"') + '"';
} else {
txt += '\n' + spacing + '"' + key + '": ' + obj[key];
};
txt += ',';
};
return txt.substr(0, txt.length - 1);
};
};
return (Array.isArray(obj) ? '[' + recur(obj, space, true) + '\n' + ']' : '{' + recur(obj, space, false) + '\n' + '}');
};
document.getElementById("json").innerText = print(data, ['large'], '\t');
<html>
<body>
<pre id="json">
</pre>
</body>
</html>
Here, a recursive function is used, which loops through the keys of an object and appends the key-value pair to a string, handling for different data types. If the key
is found in the exclude
array, then it is stringified.
By looping through the keys and conditionally applying JSON.stringify
where required, you are effectively avoiding the above issue.
I'm sure there may be edge cases to watch out for when writing the JSON yourself instead of using a built-in function, but you could adapt something like this to suit your specific needs.
EDIT: answer updated to better consider arrays within the object.