从通用类型创建新类型

Create new type from generic type

我正在尝试基于通用类型创建新的对象类型,但我的打字稿一直说它是不正确的类型。 例如:我想将所有对象属性转换为字符串。

-- 编辑示例使其更完整 条目类型

interface Example {
  value1: string;
  value2: number;
  value3: {
     value4: boolean
  }
}

// Expected new type
interface NewExample {
  value1: string;
  value2: string;
  value3: {
   value4: string
 }
}

我当前的函数基本上将所有对象属性转换为字符串:(假设没有数组)

public convertObject<T>(obj: T) {
    Object.keys(obj).forEach( prop => {
        const value = obj[prop];
        if(typeof value === 'object') 
            obj[prop] = this.convertObject(obj[prop])
        else 
            obj[prop] = 'example'+obj[prop]
    })
    return obj;
}

鉴于我将函数应用于已定义的对象,我想要的只是任何 属性 的每个“typeof”都是一个字符串。

我怎样才能做到这一点?

编辑:

  let a = { _number: 1, _obj: {mock: true} };
    let b = this.convertObject(a);
    b._obj; // This should me an object
    b._obj.mock; // This should me a string

type ValueOf<O> = O[keyof O];

function convertObject<O extends Record<string, any>, V extends ValueOf<O>>(
  obj: O
): V extends Record<any, any>
  ? Record<keyof O, Record<keyof V, string>>
  : Record<keyof O, string> {
  (Object.keys(obj) as Array<keyof O>).forEach((prop) => {
    const value = obj[prop];
    if (typeof value === 'object') {
      obj[prop] = convertObject(obj[prop]) as ValueOf<O>;
    } else {
      obj[prop] = ('example' + obj[prop]) as ValueOf<O>;
    }
  });
  return obj;
}

const hello: Example = {
  value1: 'fdsafd',
  value2: 444,
  value3: {
    value4: true
  },
  another: {
    thing: 3213
  }
};

const thing = convertObject(hello);

thing 应该在那里正确输入,每个值都是一个字符串。唯一的问题是你必须添加 as 语句,否则 TS 编译器会抱怨类型不兼容。

您可以将这种类型转换表达如下:

type ConvertObject<T> = 
   T extends object ? { [K in keyof T]: ConvertObject<T[K]> } : string;

这是一个 conditional type which checks if its input type T is an object, and if so, evaluates to mapped type,其中每个 属性 都被递归转换;如果不是,则计算结果为 string。您可以验证它是否对 Example:

做了正确的事情
type NewExample = ConvertObject<Example>;
/* type NewExample = {
    value1: string;
    value2: string;
    value3: {
        value4: string;
    };
} */

对于 convertObject() 的实际实现,需要 hard/impossible 让编译器相信您正在做的是类型安全的(参见 microsoft/TypeScript#33912 for the current state of affairs with implementing functions whose call signatures are generic conditional types), so you will probably need to use type assertions 或等价物以放松类型检查在实现内部足以避免编译器错误。

这是一种方法:

function convertObject<T extends object>(obj: T): ConvertObject<T> {
  const ret: any = {};
  Object.keys(obj).forEach(prop => {
    const value = (obj as any)[prop];
    ret[prop] = typeof value === 'object' ? convertObject(value) : ('example ' + value);
  })
  return ret;
}

请注意,我已将其设为独立函数(而不是 class 方法),并且它不会改变传入的 obj,但 returns 一个新对象 ret。 TypeScript 的类型系统确实无法模拟类型(例如)number 更改为 string 的情况。因此,如果您调用 convertObject(obj),编译器看到的 obj 的类型将不会改变,并且您 运行 如果您确实更改了 运行 时间错误的风险obj 的类型。所以我建议避免这种突变;如果你真的需要改变传入的obj,你应该非常小心,以后不要再使用obj

还要注意实现中的类型松动;我将 obj 视为 any 并使 ret 成为 any 类型,这样编译器就不会报错。这将维护类型安全的负担放在了我(或你)身上,因此我们应该检查并仔细检查 convertObject() 在其输入类型为 [=14= 时确实产生了类型 ConvertObject<T> 的输出].


让我们测试一下是否有效:

let a = { _number: 1, _obj: { mock: true } };
let b = convertObject(a);
/* let b: {
    _number: string;
    _obj: {
        mock: string;
    };
} */
console.log(b._number.toUpperCase()); // EXAMPLE 1
console.log(b._obj.mock.toUpperCase()); // EXAMPLE TRUE

看起来不错。编译器认为 b 的类型正是您想要的,并且实现也有效。

Playground link to code