I have a Record of data that could become large. I'm wondering if is it possible to enforce the key of the Record to be the same as the inner name value?.
interface Person<T> {
name: T
title: string
description: string
}
type People = Record<string, Person<string>>
// example data
const data: People = {
sarah: {
name: 'sarah',
title: 'Sarah',
description: 'Hello',
}
}
const badData: People = {
adam: {
name: 'john', // This would cause a typescript error ideally.
...
}
}
I've tried setting up People with a generic but it would require me to add all the keys to a union type which I would rather avoid. keyof
doesn't work as the object isn't defined where the keyof
is needed.
type People<T> = Record<T, Person<T>>
const people: People<keyof typeof people> = {} // Block-scoped variable 'people' used before its declaration.ts(2448)
In order to do that, you need to use extra 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',
}
})
Validate
iterates over each object key
/Name
and checks whether top level name is equal Object[Name]['name']
. If yes - return same nested object. If no - return same nested object but with iverriden name
proeprty never
.
Hence, you are getting error in a place which should be fixed.
If you are interested in Type Inference on function arguments you can check my article
More generic 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 consider 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',
},
});