TypeScript:推送到嵌套 JSON 对象的数组

TypeScript: Push to an array of Nested JSON Object

我正在尝试从 NESTED json 对象的数组中添加和删除值。删除很容易,但我很难添加:

export const addNestedJson = (
  json: Record<string, any> | any[],
  value: any,
  keys: string[]
): Record<string, any> | any[] => {
  // Please pass in mutated Values before calling the function

  const isJSONArray = Array.isArray(json);
  if (keys.length === 0) {
    if (isJSONArray) {
      return [...json, value];
    }
    return json;
  }
  const key = keys.shift() as string;
  const index = key ?? 0;
  const indexKey = isNaN(+index) ? 0 : +index;
  const newJson = isJSONArray ? json[indexKey] : json[key];
  const nestedJsonValue = addNestedJson(newJson, value, keys);
  return isJSONArray
    ? [...json, nestedJsonValue]
    : { ...json, [key]: nestedJsonValue };
}

我正在尝试使用递归,因为这似乎是最简单的解决方案,但出于某种原因,我无法可视化和制定合并逻辑。

这是我的预期:

JSON: [], key: [], valueTobeAdded: 1 should return [1]

JSON: [{a: {b:[]}}], key: ['a','b'], valueTobeAdded: 1 should return [{a:{b:[1]}}]

JSON: [{a: {b:[{c:[4]}]}}], key: ['a','b','0','c'], valueTobeAdded: 1 should return [{a: {b:[{c:[4,1]}]}}]

首先,我认为预期输出的最后两个例子没有定义正确的key。由于输入是 arrays 在其顶层,并且您希望第一个数组值被替换,因此您应该将 0 作为 key.[=21 中的第一个条目=]

在代码中我看到了两个问题并有更多的评论:

  1. 如果keys是空数组,数据不是数组,应该替换为新值,所以而不是 return json,你应该 return value

  2. 从递归调用返回时,当前级别是一个数组,新值不应该追加到数组,而是indexKey.

    处的替换值
  3. 不是真正的问题,但我不会变异 keys

  4. 不是算法的问题,而是你的属性中的名字json和变量名不合适。 JSON 是您必须传递给 JSON.parse 的内容。其他任何东西都不应该被称为 JSON.

这导致以下改编代码:

const addNested = (
  data: Record<string, any> | any[],
  value: any,
  keys: string[]
): Record<string, any> | any[] => {
  const isArray = Array.isArray(data);
  if (keys.length === 0) {
    if (isArray) {
      return [...data, value];
    }
    return value; // Overwrite any data
  }
  // Don't mutate keys with shift
  const key = keys[0] as string;
  const indexKey = +key || 0;
  const newData = isArray ? data[indexKey] : data[key];
  const nestedValue = addNested(newData, value, keys.slice(1));
  return isArray
    ? Object.assign([], data, { [indexKey]: nestedValue }) // Replace at index
    : { ...data, [key]: nestedValue };
}

const addNested = (
  data /*: Record<string, any> | any[] */,
  value /*: any */,
  keys /*: string[] */
) /*: Record<string, any> | any[] */ => {
  const isArray = Array.isArray(data);
  if (keys.length === 0) {
    if (isArray) {
      return [...data, value];
    }
    return value; // Overwrite any data
  }
  // Don't mutate keys with shift
  const key = keys[0] /* as string */;
  const indexKey = +key || 0;
  const newData = isArray ? data[indexKey] : data[key];
  const nestedValue = addNested(newData, value, keys.slice(1));
  return isArray
    ? Object.assign([], data, { [indexKey]: nestedValue }) // Replace at index
    : { ...data, [key]: nestedValue };
}

console.log(addNested([], 1, [])); //  [1]
console.log(addNested([{a: {b:[]}}], 1, [0,'a','b'])); // [{a:{b:[1]}}]
console.log(addNested([{a: {b:[{c:[4]}]}}], 1, [0,'a','b','0','c'])); // [{a: {b:[{c:[4,1]}]}}]

使用算法时的几点建议:

  1. 递归对于人脑来说其实是相当困难的。尽管我同意如果您知道如何应用递归,某些任务会更容易。特别是在嵌套递归的情况下。
  2. 命名真的很重要。

因此,例如,对我来说,“键”非常令人困惑。如果我稍后重新访问此函数,我可能首先会认为我应该将值添加到所有这些键。所以我宁愿将变量重命名为“keyPath”之类的smth。

现在,我将尝试解释我将如何解决它。这可能不是最好的解决方案,只是我的想法。

所以,假设我们有一个嵌套对象:

{
    key1: {
        key2: {
            key3: []
        }
    }
}

然后,让我们想象一个最简单的场景,我们需要一直插入一个值到key3

在这种情况下,显然keyPath=["key1", "key2", "key3"]

现在,为了到达最后一个键,我们可以从路径的第一个键开始,并尝试向下进入对象,直到到达 keyPath 的末尾。

  1. 获取 keyPath 的第一个元素。 const key = "key1";
  2. 深入了解我们的对象:const nestedObj = obj[key];
  3. 重复,但现在使用 nestedObj 而不是 obj 和“key2”,依此类推

这可以是递归,但我认为这是一个简单的循环,不是吗?

for (const i=0; i<keyPath.length; i++) {
   const key = keyPath[i];
   const newObj = obj[key];
   obj = newObj;
}

虽然这段代码有点过于冗长。我们可以这样简化它:

for (const key of keyPath) {
    obj = obj[key];
}

瞧,我们找到了可以插入元素的最终对象:

obj.push(value);

所以整个函数看起来像这样:

function addNested(obj: any, keyPath: (string|number)[], value: any) {
    for (const key of keyPath)
        obj = obj[key];
    obj.push(value);
}

这已经满足您的所有测试用例,但据我了解您的代码,还有一种可能性:keyPath 中的最后一个键不存在或者是原始值(即不是对象并且不是数组)。

好吧,当我们循环进入对象时,如果键不存在,我们的 obj[key] 将 return undefined。我们还可以检查 null.

所以在这种情况下,我们知道我们到达了对象的末尾,我们只需要分配我们的值。

if (typeof obj[key] === 'undefined' || obj[key] === null || obj[key] !== Object(obj[key])) {
    obj[key] = value;
    return;
}

typeof obj[key] === 'undefined' || obj[key] === null可以简化为obj[key] == null(注意是==而不是===)。

所以函数变成了这样:

function addNested(obj: any, keyPath: (string|number)[], value: any) {
    for (const key of keyPath) {
        if (obj[key] == null || obj[key] !== Object(obj[key])) {
            obj[key] = value;
            return;
        }
        obj = obj[key];
    }
    obj.push(value);
}

这仍然不是理想的算法,因为它没有进行任何错误检查。例如,如果对象中不存在 keyPath 中的 1 个以上的键,或者 keyPath 以对象结尾,则不会处理。

不过,我希望这个例子能帮助您改进使用算法的方法:)