I want to make sure I strong type nested field and enforce that provided type has all keys start with specific text. E.g
type Prefix = `key_${string}`;
type Container<T extends Record<Prefix, any>> = {
data: T;
};
type ValidType = {
key_1: string;
key_2: number;
};
type InvalidType = {
key_1: string;
blah: number;
};
// This is allowed - all good.
const container: Container<ValidType> = {
data: {
key_1: 'hello',
key_2: 123,
},
};
// This is also allowed - but shouldn't be!
const container_2: Container<InvalidType> = {
data: {
key_1: 'hello',
blah: 231,
},
};
The behavior I want is whenever someone tries to do Container<InvalidType>
typescript should give an error.
UPDATE: Another approach I am thinking to try is
type DataType<T> = {
[key in keyof T]: T[key];
};
But not sure how to also enforce key above to start with "key_"
CodePudding user response:
We can modify Container
to give it a mapped type where we filter out any non-prefixed fields.
type Container<T> = {
data: {
[K in keyof T as K extends Prefix ? K : never]: T[K]
}
};
Assignment of blah
will now fail:
// This is allowed - all good.
const container: Container<ValidType> = {
data: {
key_1: 'hello',
key_2: 123,
},
};
const container_2: Container<InvalidType> = {
data: {
key_1: 'hello',
blah: 231,
// ^^^^^^^^^ Error: Type '{ key_1: string; blah: number; }' is not assignable to type '{ key_1: string; }
},
};
But be aware! Due to TypeScript's structural type system, excess properties are allowed in most places. We only get an error here because we directly assign an object literal.
To have "real" excess property checks, we need to blacklist the unwanted properties by giving them a never
type.
type Container<T> = {
data: {
[K in keyof T as K extends Prefix ? K : never]: T[K]
} & {
[K in keyof T as K extends Prefix ? never : K]?: never
}
};
Now this will fail too:
const container_3 = {
data: {
key_1: 'hello',
blah: 123,
},
};
const container_4: Container<InvalidType> = container_3