如何以可逆的方式将任意嵌套的 object 转换为表格结构?

How to convert an arbitrarily nested object into tabular structure a reversible way?

给定一个任意深度的嵌套object,其结构直到运行时才知道,例如

    {
      "row-0" : {
        "rec-0" : {
          "date" : 20220121,
          "tags" : [ "val-0" ]
        },
        "rec-1" : {
          "date" : 20220116,
          "url" : "https://example.com/a",
          "tags" : [ "val-0", "val-1" ]
        }
      },
      "row-1" : {
        "rec-0" : {
          "date" : 20220116,
          "url" : "https://example.com/b"
        }
      }
    }

我想要一个工具/程序将其可逆地转换为表格(2D)结构,例如

    {
      "row-0" : {
        "['rec-0']['date']" : 20220121,
        "['rec-0']['tags'][0]" : "val-0",
        "['rec-1']['date']" : 20220116,
        "['rec-1']['url']" : "https://example.com/a",
        "['rec-1']['tags'][0]" : "val-0",
        "['rec-1']['tags'][1]" : "val-1"
        },
      "row-1" : {
        "['rec-0']['date']" : 20220116,
        "['rec-0']['url'']" : "https://example.com/b"       
      }
    }

这种格式会导致导出为 CSV 文件并随后使用电子表格应用程序进行编辑。将原来嵌套的object的路径编码在"['rec-0']['date']""['rec-0']['tags'][0]"等key(headers列)中,方便逆向变换

实现此目标的最佳方法是什么?

您可以使用一个简单的递归函数来完成它,该函数生成一个嵌套的键名和结束值并将其填充到一个数组中。

Object.entries 在解决方案中很方便,因为它可以分别使用 indexkey 迭代 arrayobject

const data = { "row-0": { "rec-0": { date: 20220121, tags: ["val-0"], }, "rec-1": { date: 20220116, url: "https://example.com/a", tags: ["val-0", "val-1"], }, }, "row-1": { "rec-0": { date: 20220116, url: "https://example.com/b", }, }, };

const generateNestedKeyNameAndValue = (input, nestedKeyName, keyValueArr) => {
  if (typeof input === "object") {
    // array or object - iterate over them
    const quoteString = Array.isArray(input) ? "" : "'";
    Object.entries(input).forEach(([key, value]) => {
      generateNestedKeyNameAndValue(
        value,
        // extend the key name 
        `${nestedKeyName}[${quoteString}${key}${quoteString}]`,
        keyValueArr
      );
    });
  } else {
    // string or number (end value)
    keyValueArr.push([nestedKeyName, input]);
  }
};

const output = Object.fromEntries(
  Object.entries(data).map(([key, value]) => {
    const generatedKeyValuePairs = [];
    generateNestedKeyNameAndValue(value, "", generatedKeyValuePairs);
    return [key, Object.fromEntries(generatedKeyValuePairs)];
  })
);

console.log(output);

使用 Array.map() 迭代当前条目。根据规则生成路径(见preparePath函数)。对于每个值,检查它是否是一个对象(或数组)。如果它是将它的键添加到路径中。如果不是,return 一个 { [path]: val } 对象。通过展开到 Object.assing().

来展平所有对象

// prepare the key from the current key, isArray(obj), and the previous path
const preparePath = (key, obj, path = []) => [
  ...path, 
  `[${Array.isArray(obj) ? key : `'${key}'`}]`
]

// convert all sub objects to a single object
const fn = (obj, path) => Object.assign({}, ...Object.entries(obj)
  .map(([key, val]) => typeof val === 'object' // if the value is an object
    ? fn(val, preparePath(key, obj, path)) // iterate it and it to current path
    : { [preparePath(key, obj, path).join('')]: val } // if the val is not an object, create an object of { [path]: val }
  ))

const data = {"row-0":{"rec-0":{"date":20220121,"tags":["val-0"]},"rec-1":{"date":20220116,"url":"https://example.com/a","tags":["val-0","val-1"]}},"row-1":{"rec-0":{"date":20220116,"url":"https://example.com/b"}}}

// iterate the 1st level of the object since and flatten each sub object
const result = Object.fromEntries(
  Object.entries(data).map(([k, v]) => [k, fn(v)])
)

console.log(result)