Typescript深度合并函数,无需使用类型’any’

huangapple go评论67阅读模式
英文:

Typescript merge deep function without type 'any'

问题

如何将此答案转换为 TypeScript 而不使用类型 'any'?

目前(不起作用)尝试:

export function isObject(item: object): boolean {
  return !!item && typeof item === "object" && !Array.isArray(item);
}

export function mergeDeep(target: object, ...sources: object[]): object {
  if (!sources?.length) return target;
  const source = sources.shift();

  if (isObject(target) && isObject(source)) {
    for (const key in source) {
      const sk = source[key];
      if (isObject(sk)) {
        if (!target[key]) Object.assign(target, { [key]: {} });
        mergeDeep(target[key], sk);
      } else {
        Object.assign(target, { [key]: source[key] });
      }
    }
  }

  return mergeDeep(target, ...sources);
}


const obj1 = {
  one: {
    two: {
      keep: "yes!",
      change: "should not see me",
    },

    three: {
      four: {
        keep: "yes!",
        change: "should not see me",
      },
    },
  },
};

const obj2 = {
  one: {
    two: {
      change: "NICE 1!!",
    },

    three: {
      four: {
        change: "NICE 2!!",
      },
    },
  },
};

console.log(JSON.stringify(mergeDeep(obj1, obj2), null, 2));
英文:

How to convert [this answer][1] to typescript without using the type 'any'?

Currently (not working) try:

export function isObject(item: object): boolean {
return !!item && typeof item === "object" && !Array.isArray(item);
}
export function mergeDeep(target: object, ...sources: object[]): object {
if (!sources?.length) return target;
const source = sources.shift();
if (isObject(target) && isObject(source)) {
for (const key in source) {
const sk = source[key];
if (isObject(sk)) {
if (!target[key]) Object.assign(target, { [key]: {} });
mergeDeep(target[key], sk);
} else {
Object.assign(target, { [key]: source[key] });
}
}
}
return mergeDeep(target, ...sources);
}
const obj1 = {
one: {
two: {
keep: "yes!",
change: "should not see me",
},
three: {
four: {
keep: "yes!",
change: "should not see me",
},
},
},
};
const obj2 = {
one: {
two: {
change: "NICE 1!!",
},
three: {
four: {
change: "NICE 2!!",
},
},
},
};
console.log(JSON.stringify(mergeDeep(obj1, obj2), null, 2));

答案1

得分: 1

如果您并不真的关心使用非常强类型,只是想让事情编译而不使用不安全的 any 类型,您可以使用更安全的 unknown 类型。对于 isObject(),您可以将其标记为自定义类型保护函数,以便结果将缩小输入到允许访问任何属性键的对象类型:

function isObject(item: unknown): item is Record<string, unknown> {
    return !!item && typeof item === "object" && !Array.isArray(item);
}

function mergeDeep(target: unknown, ...sources: unknown[]): unknown {
    if (!sources?.length) return target;
    const source = sources.shift();
    if (isObject(target) && isObject(source)) {
        for (const key in source) {
            const sk = source[key];
            if (isObject(sk)) {
                let tk = target[key];
                if (!tk) tk = target[key] = {};
                mergeDeep(tk, sk);
            } else {
                Object.assign(target, { [key]: source[key] });
            }
        }
    }
    return mergeDeep(target, ...sources);
}

这基本上是相同的内容,但我将 target[key] 更改为保存的 tk 变量,因为编译器无法轻松确定如果键是任意的 string,则对索引访问的结果会发生什么。有关此问题的相关开放问题,请参见 microsoft/TypeScript#10530

请注意,当完成时,输出只是 unknown 类型。您可能希望输出类型与输入更密切地关联,以便,例如,mergeDeep({a: 123}).a 已知存在且类型为 number。但这超出了问题的范围。

Playground 上的代码链接

英文:

If you don't really care about using very strong types and you just want to get things to compile without the unsafe any type, you can use the safer unknown type. And for isObject() you can mark it as a custom type guard function, so that the result will narrow the input to an object type which allows any property key to be accessed:

function isObject(item: unknown): item is Record&lt;string, unknown&gt; {
return !!item &amp;&amp; typeof item === &quot;object&quot; &amp;&amp; !Array.isArray(item);
}
function mergeDeep(target: unknown, ...sources: unknown[]): unknown {
if (!sources?.length) return target;
const source = sources.shift();
if (isObject(target) &amp;&amp; isObject(source)) {
for (const key in source) {
const sk = source[key];
if (isObject(sk)) {
let tk = target[key];
if (!tk) tk = target[key] = {};
mergeDeep(tk, sk);
} else {
Object.assign(target, { [key]: source[key] });
}
}
}
return mergeDeep(target, ...sources);
}

That's pretty much the same thing but I changed target[key] to a saved tk variable because the compiler can't easily tell what happens to an indexed access like that if the key is an arbitrary string. See microsoft/TypeScript#10530 for the relevant open issue about that.

Note that when you're done, the output is just of type unknown. You might want the output type to be something more closely tied to the input, so that, for example, mergeDeep({a: 123}).a is known to exist and be of type number. But that's out of scope for the question as asked.

Playground link to code

huangapple
  • 本文由 发表于 2023年7月7日 02:45:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/76631720.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定