To achieve this, it is necessary to utilize an additional function.
interface Person<T> {
name: T
title: string
description: string
}
type People = Record<string, Person<string>>
type Validate<T extends People> = {
[Name in keyof T]: Name extends T[Name]['name'] ? T[Name] : T[Name] & { name: never }
}
const validator = <
Name extends string,
Human extends Person<Name>,
Data extends Record<Name, Human>
>(data: Validate<Data>) => data
const result = validator({
sarah: {
name: 'sarah',
title: 'Sarah',
description: 'Hello',
},
adam: {
name: 'john', // Error.
title: 'Sarah',
description: 'Hello',
}
})
Playground
Validate
goes through each object key
/Name
and verifies if the top-level name matches Object[Name]['name']
. If it does, it returns the same nested object. If not, it returns the same nested object but with the name
property overridden as never
.
Therefore, the error you encounter points out the area that needs fixing.
If you're interested in Type Inference for function arguments, you may want to read my article
A more generalized version of Validation
from @Konrad Madej:
type Validate<Data extends People> = {
[Name in keyof Data]: Name extends Data[Name]['name'] ? Data[Name] : Omit<Data[Name], 'name'> & { name: Name }
}
If you have numeric keys, please refer to this example:
interface PayloadObj<T> {
elementId: T;
values: any;
}
type Payload = Record<number, PayloadObj<number>>;
type Validate<T extends Payload> = {
[Key in keyof T]: T[Key] & { elementId: Key }
};
const validator = <
ElementId extends number,
PayloadValue extends PayloadObj<ElementId>,
Data extends Record<ElementId, PayloadValue>
>(data: Validate<Data> & Data): Payload => data;
const result = validator({
0: {
elementId: 0,
values: 'Hello',
},
1: {
elementId: 2, // Error.
values: 'World',
},
});
Playground