使用 "optional" 参数叶键值定义嵌套对象,在打字稿中未定义

Define nested object with "optional" parameters leaf key values as undefined in typescript

这是 的后续问题。 这里的对象可以有可选参数,undefinedAllLeadNodes 如下所示

Input:
class Person {
    name: string = 'name';
    address: {street?: string, pincode?: string} = {};
}

const undefperson = undefinedAllLeadNodes(new Person);
console.log(undefperson);

Output:
Person: {
  "name": undefined,
  "address": undefined
} 

如您所见,地址没有属性,它应该 return 未定义。

如何确保 Undefine(defined ) 类型处理这个问题? 目前它接受 undefperson.address.street = '';

但我想让它抛出“地址可能未定义”的错误

更新:

export function undefineAllLeafProperties<T extends object>(obj : T) {

    const keys : Array<keyof T> = Object.keys(obj) as Array<keyof T>;

    if(keys.length === 0) 
        return undefined!;//This makes sure address is set to undefined. Now how to identify this with typescript conditionals so that when accessing undefperson.address.street it should say address may be undefined.

    keys.forEach(key => {
    
        if (obj[key] && typeof obj[key] === "object" && !Array.isArray(obj[key])) {
            obj[key] = undefineAllLeafProperties(<any>obj[key]) as T[keyof T];
        } else if(obj[key] && Array.isArray(obj[key])) {
            obj[key] = undefined!;
        } else {
            obj[key] = undefined!;
        }
    });

    return obj;
}

首先我们需要几个助手类型:

type HasNoKeys<T extends object> = keyof T extends never ? 1 : 0

type RequiredOnly<T> = {
    [K in keyof T as T[K] extends Required<T>[K] ? K : never]: T[K]
}

第一个检查传递的 T 对象是否没有键。第二个有点复杂。我们正在使用 remappine kyes in mapped types 功能来删除可选字段。

最后,仅当 T 对象没有必填字段(或根本没有字段,因为没有字段的对象也没有必填字段)时,才将这些类型合并为 undefined

type UndefinedIfHasNoRequired<T> = 
    HasNoKeys<RequiredOnly<T>> extends 1 ? undefined : never

最终类型将如下所示:

type Undefine<T extends object> = {
    [K in keyof T]: T[K] extends object 
        ? Undefine<T[K]> | UndefinedIfHasNoRequired<T[K]> 
        : T[K] | undefined
}

playground link

这里我们只在没有必填字段的情况下将 | undefined 添加到对象字段的类型。

虽然现在你必须向打字稿保证你的内部属性在尝试为它们的字段赋值时不是 undefined:

class OptionalPerson {
    name: string = 'name';
    address: {street?: string, pincode?: string} = {street: 'street', pincode: '44555'};
}

const undefOptionalPerson = undefineAllLeafProperties(new OptionalPerson())

undefOptionalPerson.address.street = '' // error

undefOptionalPerson.address!.street = ''
// or
if (undefOptionalPerson.address) {
    undefPerson.address.street = ''
}

在第一种情况下,我们使用 non-null assertion 运算符让打字稿相信我们对象的 address 字段不是 undefined。但请记住,如果实际上它仍然是 undefined,您会在此处遇到运行时错误。

在第二种情况下,我们使用合法的 type narrowing to actually check whether the field has truthy 值。考虑到它的类型 object | undefined 此检查会丢弃 | undefined 部分。

您可以将您的 class 人更新为关注者,这应该有效。

class Person {
    name: string = 'name';
    address: { street?: string, pincode?: string } | undefined = { street: 'street', pincode: '44555' };
}

找到Playground Link

更新

你也可以像下面那样做不同的事情

type Undefine<T extends object> = Id<{
    [K in keyof T]: T[K] extends object ? Undefine<T[K]> | undefined : T[K] | undefined
}>

找到 Playground Link

注:

type Person {
   name?: string
   address?: {
      street?: string
      pincode?: string
   } 
}

相当于

type Person {
   name: string | undefined
   address: {
      street: string | undefined
      pincode: string | undefined
   } | undefined
}

在打字稿中 ? 用于表示可选参数,基本上是 undefined