I want format like this:
var house = {
family: {
surname: "Smith",
people: 4,
pets: {
dogs: {number:1, names:["toby"]},
cats: {number:2, names:["bob", "boo"]},
platypus: {number:1, names:["perry"], codename: ["agent p"]},
}
},
livingRoom: [
{name:"couch", amount:2},
{name:"shelf", amount:1},
{name:"nightstand", amount:1},
{name:"television", amount:1},
],
bedroom: [
{name:"bed", amount:1},
{name:"wardrobe", amount:1},
{name:"shelf", amount:2},
],
}
It is a similar problem, but with more specific object keys, within a more complex tree of objects, and it started to get quite complicated to do this with just a regex. So, I created a function using toJSON and the stringify replacer as tools to handle this.
The function I created has some limitations, like just being able to define singleLine and multiLine only objects, and not numeric or text lists for example. It also has the limitation of some specific characters that are used for a replacement scheme, they are unusual characters, but look to confirm if you must replace them. The characters are four: '╲' (fake \), '”' (fake "), '→' and '←', if your object contains any of them you can replace them at the very beginning of the function.
Here an example with the function working:
// Custom Stringfy
const customStringify = function (
obj,
replacer,
space = "\t",
{
singlelineObjectKeys = [],
multilineObjectKeys = [],
singlelineChildKeys = [],
multilineChildKeys = [],
singlelineInsideList = [],
}
) {
// WARNING
// - This function will make a mess if your Object contain some of the following characters:
const fakeNewLine = `╗`; // (replace \n in middle of process)
const fakeTab = `╦`; // (replace \t in middle of process)
const fakeQuote = `║`; // (replace " in middle of process)
const startString = `╠`; // (add to start in middle of process)
const endString = `╣`; // (add to end in middle of process)
// So a solution in this case can be replace this chars by others not used (dont use characters that can mess the regex)
// First make a stringify to solve any toJSON in the main object, then copy the main object stringfied to create all the necessary new toJSON
let objModified = JSON.parse(JSON.stringify(obj, replacer));
// Convert an entire object to single line string
const singleLineOBJ = function () {
// To not change any toJSON
const obj = Array.isArray(this) ? [...this] : { ...this };
// To not fall in a stringify loop
delete obj.toJSON;
// Mark the startString and endString
return (
startString +
JSON.stringify(obj)
// Replace all " by fakeQuote
.replace(/"/g, fakeQuote) +
endString
);
};
// Convert an entire object to multi line string
const multiLineOBJ = function () {
// To not change any toJSON
const obj = Array.isArray(this) ? [...this] : { ...this };
// To not fall in a stringify loop
delete obj.toJSON;
// Mark the startString and endString
return (
startString +
JSON.stringify(obj, null, "\t")
// Replace all " by fakeQuote
.replace(/"/g, fakeQuote)
// Replace \n using fakeNewLine
.replace(/\n/g, fakeNewLine)
// Replace \t using fakeTab
.replace(/\t/g, fakeTab) +
endString
);
};
// Checks all keys on the object
const throughEveryKey = function (key, value) {
let obj = this;
// objects inside specific keys to become single-line
if (singlelineObjectKeys.includes(key)) {
obj[key].toJSON = singleLineOBJ;
}
// objects inside specific keys to become multi-line
if (multilineObjectKeys.includes(key)) {
obj[key].toJSON = multiLineOBJ;
}
// objects containing the following keys to become single-line
if (singlelineChildKeys.includes(key)) {
obj.toJSON = singleLineOBJ;
}
// objects containing the following keys to become multi-line
if (multilineChildKeys.includes(key)) {
obj.toJSON = multiLineOBJ;
}
// names of list of objects to each list-item become single-line
if (singlelineInsideList.includes(key)) {
obj[key].forEach(
(objectInsideList) => (objectInsideList.toJSON = singleLineOBJ)
);
}
return value;
};
// Just use stringify to go through all object keys, and apply the function to implement "toJSON" in right places, the result of stringify is not used in this case (WIP)
JSON.stringify(objModified, throughEveryKey);
// Use stringfy with right replacers, end result
return (
JSON.stringify(objModified, null, "\t")
// Put in all start of line the right number of Tabs
.replace(new RegExp("(?:(?<=(?<leadTab>^\t*).+?)(?<newLine>" + fakeNewLine + ")(?=.+?))+", "gm"), "$&$1")
// Replace the fake tab by the real one
.replace(new RegExp(fakeTab, "gm"), "\t")
// Replace the fake new line by the real one
.replace(new RegExp(fakeNewLine, "gm"), "\n")
// Replace the fake quote by the real one
.replace(new RegExp(fakeQuote, "gm"), '"')
// Remove start and end of line from the stringfied object
.replace(new RegExp('"' + startString, "gm"), "")
.replace(new RegExp(endString + '"', "gm"), "")
// Replace tab by custom space
.replace(/(?<=^\t*)\t/gm, space)
);
};
var house = {
family: {
surname: "Smith",
people: 4,
pets: {
dogs: {
number: 1,
names: ["toby"],
},
cats: {
number: 2,
names: ["bob", "boo"],
},
platypus: {
number: 1,
names: ["perry"],
codename: ["agent p"],
},
},
},
livingRoom: [
{
name: "couch",
amount: 2,
},
{
name: "shelf",
amount: 1,
},
{
name: "nightstand",
amount: 1,
},
{
name: "television",
amount: 1,
},
],
bedroom: [
{
name: "bed",
amount: 1,
},
{
name: "wardrobe",
amount: 1,
},
{
name: "shelf",
amount: 2,
},
],
};
console.log("A custom stringify:\n\n");
console.log(
customStringify(house, null, " ", {
singlelineObjectKeys: ["dogs", "cats", "platypus"],
multilineObjectKeys: ["family"],
multilineChildKeys: [],
singlelineChildKeys: [],
singlelineInsideList: ["livingRoom", "bedroom"],
})
);
console.log("\n\n\n");
console.log("A normal stringify:\n\n");
console.log(JSON.stringify(house, null, " "));
You need to pass some information to work, you don't need to use everything, but I'll explain the cases that I left prepared to format between multiLine and singleLine:
singlelineObjectKeys: Put here the keys for objects / arrays that need to be on a single line
multilineObjectKeys: Place here the keys of objects / arrays that need to be formatted in several lines
multilineChildKeys: If you do not want to specify a parent object, as they can have many, specify a child key for the parent object to be formatted on multiple lines
singlelineChildKeys: If you don't want to specify a parent object, as they can have many, specify a child key for the parent object to be formatted on a single line
singlelineInsideList: If you have a list of objects, and you want all objects within that list to be formatted in a single list, put the key for that list here
In the example above the code used was this:
customStringify(house, null, '\t', {
singlelineObjectKeys: ["dogs","cats","platypus"],
multilineObjectKeys: ["family"],
singlelineInsideList: ["livingRoom","bedroom"],
})
But this will result the same for example:
customStringify(house, null, '\t', {
singlelineChildKeys: ["name","names"],
})
As I had researched this in several places and found nothing about it, I register my solution here (which was one of the places I looked for). Feel free to use!
Edit:
- Updated to receive lists as single line or multiline on objects, thanks to @mj1701's suggestion.
- Updated way to replace newline and tab, made code 20% faster
- Old unresolved problem: After defining singlelineInsideList, if you put a multilineObjectKeys that is inside, the code tab has a problem, in progress
I am modifying the code in free time, it is not 100% finished, but it already works in several tested cases.
If you found any bugs, please comment here!