JSON.对自定义格式进行字符串化

JSON.stringify custom formatting

本文关键字:字符串 格式 自定义 JSON      更新时间:2023-09-26

我正在寻找一种将JSON对象写入文件的方法,但与原始格式保持相同。我已经设法使用writeFileSync(路径,数据)和JSON.stringify()编写内容,但努力弄清楚如何自定义格式。选项JSON。Stringify似乎只对空格的数量进行格式化。

是否有任何方法使用JSON。Stringify生成以下格式

{
  "key1"           :{"key": "value1","key": "value2"},
  "key2"           :{"key": "value1","key": "value2"}
}

而不是默认生成的

{
  "key1": 
   {
     "key": "value1",
     "key": "value2"
   },
  "key2": 
   {
     "key": "value1",
     "key": "value2"
   },
}

遗憾的是,您可以使用space参数来定义对象落在树中的间距。你的建议将需要自定义格式化,使用类似正则表达式的东西来格式化字符串后,你的字符串。

下面你会发现一些示例代码来做你想做的事情。您可以将所有这些附加到单个命令中,例如JSON.stringify(myJSON, null, ' ').replace(/: '{'n's+/g, ': {').replace(/",'n's+/g, ', ').replace(/"'n's+'}/g, '}');,但是为了您可以看到我所做的,我一步一步地将其分解。

const myJSON = {
  "key1":{"key": "value1","key2": "value2"},
  "key2":{"key": "value1","key2": "value2"}
}
let myString = JSON.stringify(myJSON, null, ' ').replace(/: {/g, `${' '.repeat(5)}: '{`); //Set the key spacing
myString = myString.replace(/: '{'n's+/g, ': {'); //Bring the child bracket on the same line
myString = myString.replace(/",'n's+/g, ', '); //Bring all the other objects for that array up
myString = myString.replace(/"'n's+'}/g, '}'); //Pull the closing bracket on the same line
const myCompactString = JSON.stringify(myJSON, null, ' ').replace(/: {/g, `${' '.repeat(5)}: '{`).replace(/: '{'n's+/g, ': {').replace(/",'n's+/g, ', ').replace(/"'n's+'}/g, '}'); //Done all at once
console.log(`myString: ${myString}`);
console.log(`myCompactString: ${myCompactString}`);

以KevynTD的精彩回答为基础!

防止字符串['song', 'bird']的数组变为{'0':'song', '1': 'bird'}索引键对象。添加if (Array.isArray(this)) obj = this;到singleLineOBJ函数

    const singleLineOBJ = function () {
        let obj = { ...this }; // To not change any toJSON
        if (Array.isArray(this))
            obj = this; // override converting array into object
        delete obj.toJSON // To not fall in a stringify loop

我想要这样的格式:

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},
    ],
}

这是一个类似的问题,但有更具体的对象键,在一个更复杂的对象树中,它开始变得相当复杂,仅仅用一个正则表达式来做这件事。因此,我使用toJSON和stringify replacer作为工具创建了一个函数来处理这个问题。

我创建的函数有一些限制,比如只能定义单行和多行对象,而不能定义数字或文本列表。它也有一些用于替换方案的特定字符的限制,它们是不寻常的字符,但要确认是否必须替换它们。字符有四个:' π ' (fake '), ' ' ' (fake "), '→'和'←',如果你的对象包含它们中的任何一个,你可以在函数的开头替换它们。

下面是函数工作的例子:

// 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, "  "));

您需要传递一些信息才能工作,您不需要使用所有内容,但是我会解释我在multiLine和singleLine之间准备格式化的情况:

singlelineObjectKeys:将需要在单行上的对象/数组的键放在这里

multilineObjectKeys:将需要在几行中格式化的对象/数组的键放在这里

multilineChildKeys:如果你不想指定父对象,因为它们可以有很多,为父对象指定一个子键,以便在多行上格式化

singlelineChildKeys:如果你不想指定父对象,因为它们可以有很多,为父对象指定一个子键,以便在单行上格式化

singlelineInsideList:如果您有一个对象列表,并且您希望该列表中的所有对象都在一个列表中格式化,请将该列表的键放在这里

在上面的例子中,使用的代码是这样的:
customStringify(house, null, ''t', {
    singlelineObjectKeys: ["dogs","cats","platypus"],
    multilineObjectKeys: ["family"],
    singlelineInsideList: ["livingRoom","bedroom"],
})

但是结果是一样的,例如:

customStringify(house, null, ''t', {
    singlelineChildKeys: ["name","names"],
})

由于我在几个地方研究了这个问题,但没有找到任何关于它的信息,我在这里注册了我的解决方案(这是我寻找的地方之一)。请随意使用!

编辑:

  • 更新接收列表为单行或多行对象,感谢@mj1701的建议。
  • 更新了替换换行符和制表符的方式,使代码速度提高了20%
  • 旧的未解决的问题:在定义singlelineInsideList之后,如果你把multilineObjectKeys放在里面,代码选项卡有问题,正在进行

我在空闲时间修改代码,它不是100%完成,但它已经在几个测试用例中工作了。

如果你发现任何错误,请在这里评论!