Type checking of optional nested properties in an interface

Advertisements

I want to perform type checking on nested properties in an interface, and have the following code

type PathImpl<T, K extends keyof T> = K extends string
  ? T[K] extends Record<string, any>
    ? `${K}.${Path<T[K]>}` | K
    : K
  : never;

export type Path<T> = PathImpl<T, keyof T>;

export interface User {
  name: string;
  age: number;
  dimensions: {
    height: number;
    weight: number;  
  }
  address?: {
    street: string;
    city: string;
    state: string;
  };
}

const validPath: Path<User> = 'name'; // Works fine
const alsoValidPath: Path<User> = 'address'; // Works fine
const nestedValidPath: Path<User> = 'dimensions.height'; // Yay, works fine!
const doesntWorkPath: Path<User> = 'address.street'; // Doesn't work because address is optional

Is there a way to design the Path type to also allow ‘address.street’ to be valid, although address is optional?

Playground link

>Solution :

You could use the Required utility type in Path to mark all properties on the input type as required:

export type Path<T> = PathImpl<Required<T>, keyof T>;
// −−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^^^^^^^^

Then your final assignment with "address.street" is valid because the type of Path<User> is:

| "name" 
| "age" 
| "dimensions" 
| "address" 
| "dimensions.height" 
| "dimensions.weight" 
| "address.street" 
| "address.city" 
| "address.state"

Playground link

Leave a ReplyCancel reply