1

I have the following code

type ValidateFn = (value: any) => boolean;

type ValidationSchema<T> = {
    [P in keyof T]: Array<ValidateFn> | ValidateFn | ValidationSchema<T[P]>
}

interface Person {
    name?: string;
    age?: number;
}

const obj: ValidationSchema<Person> = {
    'name': 'test' // <-- problem is here
}

Typescript compiler is saying that the fields of ValidationSchema are either

string | ValidateFn | ValidateFn[]

Where did the string come from

1 Answer 1

1

Mapped types of the form {[K in keyof T]: ...} where T is some type parameter are called homomorphic, as introduced in microsoft/TypeScript#12447 (which calls them isomorphic). In that pull request, it says

when a primitive type is substituted for T in an isomorphic mapped type, we simply produce that primitive type.

If a primitive type like string goes in, the same primitive type comes out:

type SomeHomomorphicMappedType<T> = { [K in keyof T]: Date };
type MappedString = SomeHomomorphicMappedType<string>; // string

Since ValidationSchema is a homomorphic recursive mapped type, when it recurses down into the name and age properties of Person, it gets applied as ValidationSchema<string | undefined> for name and ValidationSchema<number | undefined> for age. Primitive in, primitive out:

type VSP = ValidationSchema<Person>
/* type VSP = {
    name?: string | ValidateFn | ValidateFn[] | undefined;
    age?: number | ValidateFn | ValidateFn[] | undefined;
} */

So, that explains it.


If you want some other behavior you might want to use conditional types to do something different when T extends object vs when it doesn't. Maybe like this:

type ValidationSchemaMaybe<T> = {
  [P in keyof T]: Array<ValidateFn> | ValidateFn | (
    T[P] extends object ? ValidationSchemaMaybe<T[P]> : never
  );
}

type VSPMaybe = ValidationSchemaMaybe<Person>
/* type VSPMaybe = {
    name?: ValidateFn | ValidateFn[] | undefined;
    age?: ValidateFn | ValidateFn[] | undefined;
} */

Okay, hope that helps; good luck!

Playground link to code

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.