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 中的第一个条目=]
在代码中我看到了两个问题并有更多的评论:
如果keys
是空数组,数据不是数组,应该替换为新值,所以而不是 return json
,你应该 return value
从递归调用返回时,当前级别是一个数组,新值不应该追加到数组,而是indexKey
.
处的替换值
不是真正的问题,但我不会变异 keys
。
不是算法的问题,而是你的属性中的名字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]}]}}]
使用算法时的几点建议:
- 递归对于人脑来说其实是相当困难的。尽管我同意如果您知道如何应用递归,某些任务会更容易。特别是在嵌套递归的情况下。
- 命名真的很重要。
因此,例如,对我来说,“键”非常令人困惑。如果我稍后重新访问此函数,我可能首先会认为我应该将值添加到所有这些键。所以我宁愿将变量重命名为“keyPath”之类的smth。
现在,我将尝试解释我将如何解决它。这可能不是最好的解决方案,只是我的想法。
所以,假设我们有一个嵌套对象:
{
key1: {
key2: {
key3: []
}
}
}
然后,让我们想象一个最简单的场景,我们需要一直插入一个值到key3
。
在这种情况下,显然keyPath=["key1", "key2", "key3"]
现在,为了到达最后一个键,我们可以从路径的第一个键开始,并尝试向下进入对象,直到到达 keyPath
的末尾。
- 获取 keyPath 的第一个元素。
const key = "key1";
- 深入了解我们的对象:
const nestedObj = obj[key];
- 重复,但现在使用
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 以对象结尾,则不会处理。
不过,我希望这个例子能帮助您改进使用算法的方法:)
我正在尝试从 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 中的第一个条目=]
在代码中我看到了两个问题并有更多的评论:
如果
keys
是空数组,数据不是数组,应该替换为新值,所以而不是return json
,你应该return value
从递归调用返回时,当前级别是一个数组,新值不应该追加到数组,而是
处的替换值indexKey
.不是真正的问题,但我不会变异
keys
。不是算法的问题,而是你的属性中的名字
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]}]}}]
使用算法时的几点建议:
- 递归对于人脑来说其实是相当困难的。尽管我同意如果您知道如何应用递归,某些任务会更容易。特别是在嵌套递归的情况下。
- 命名真的很重要。
因此,例如,对我来说,“键”非常令人困惑。如果我稍后重新访问此函数,我可能首先会认为我应该将值添加到所有这些键。所以我宁愿将变量重命名为“keyPath”之类的smth。
现在,我将尝试解释我将如何解决它。这可能不是最好的解决方案,只是我的想法。
所以,假设我们有一个嵌套对象:
{
key1: {
key2: {
key3: []
}
}
}
然后,让我们想象一个最简单的场景,我们需要一直插入一个值到key3
。
在这种情况下,显然keyPath=["key1", "key2", "key3"]
现在,为了到达最后一个键,我们可以从路径的第一个键开始,并尝试向下进入对象,直到到达 keyPath
的末尾。
- 获取 keyPath 的第一个元素。
const key = "key1";
- 深入了解我们的对象:
const nestedObj = obj[key];
- 重复,但现在使用
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 以对象结尾,则不会处理。
不过,我希望这个例子能帮助您改进使用算法的方法:)