2
votes

I'm trying to extend existing interface:

type ColDef = { field: string; }

so that I will limit field values only to actual properties of specified type:

interface TypeSafeColDef<T> extends ColDef {
   field: keyof T
}

but I'm getting:

Interface 'TypeSafeColDef' incorrectly extends interface 'ColDef'. Types of property 'field' are incompatible. Type 'keyof TRow | undefined' is not assignable to type 'string | undefined'. Type 'keyof TRow' is not assignable to type 'string | undefined'. Type 'string | number | symbol' is not assignable to type 'string | undefined'. Type 'number' is not assignable to type 'string | undefined'. Type 'keyof TRow' is not assignable to type 'string'. Type 'string | number | symbol' is not assignable to type 'string'. Type 'number' is not assignable to type 'string'

I've tried following constraint, but no success

type StringKey = { [key: string]: any }

interface TypeSageColDef<TRow extends StringKey>
1
extends { [key: string]: any } behaves same as extends object for some reason (not limiting keys to be string only). Real question is why do you need extends ColDef? Do you have more properties in real example? - Aleksey L.
I need to extend ColDef because I want to get compiler error when invalid field is passed. I do not own ColDef, it is a 3rd party type definition. - Liero

1 Answers

5
votes

Your last one comes quite close, we still need to extend ColDef and extract a string key type for field:

type ColDef = { field: string; }

interface TypeSafeColDef<T extends object> extends ColDef {
  field: Extract<keyof T, string>
}

// test
type T1 = TypeSafeColDef<{ a: string }> // { field: "a"; }

field now has type Extract<keyof T, string>, because keyof supports string | number | symbol property names since TS 2.9. There is a compiler option --keyofStringsOnly to disable this new behavior, if you don't want that.

A second alternative is to define a type alias to get rid of Extract. This works due to the nature of the intersection operator never provoking errors:

type TypeSafeColDefAlias<T extends object> = ColDef & {
  field: keyof T
}

Code sample