Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript mapped types: Flag type with nesting

Is there a way in TypeScript to create a nested version of a Flags type mentioned in Advanced types documentation?

This works well:

type Flags<T> = {
  [P in keyof T]: boolean;
}

interface Colors {
  red: string;
  green: string;
  blue: string;
}

const activeColors: Flags<Colors> = {
  red: true,
  green: true,
  blue: false
}

But what if I wanted to create let's say a NestedFlags type that would be able to handle nested objects like this?

interface NestedColors {
  red: string;
  green: string;
  blue: string;
  more: {
    yellow: string;
    violet: string;
    black: string;
  }
}

const activeNestedColors: NestedFlags<NestedColors> {
  red: true,
  blue: false,
  green: true,
  more: {
    yellow: false,
    violet: true,
    black: true
  }
}

I am able to create a NestedFlags type with [P in keyof T]: boolean | NestedFlags<T[P]>. That solution works well except the fact that it allows me to create an object with eg. more: false which is undesirable in my case.

Thank you!

like image 966
vasekch Avatar asked Mar 06 '18 19:03

vasekch


1 Answers

You probably want mapped conditional types, which will be available starting in TypeScript v2.8, to be released sometime this month (March 2018). You can use it now with typescript@next. Here's a first shot at how I'd implement it:

type NestedFlags<T> = {
  [K in keyof T]: T[K] extends object ? NestedFlags<T[K]> : boolean
}

The above line uses the conditional types ternary type syntax. It means: for every key in T, the property type for NestedFlags<T> will depend on whether the original property is an object type or not. If the original property is not an object type, the corresponding property will be boolean. If the original property is an object type, then the corresponding property will be a NestedFlags<> applied to that object type.

This gives you the following behavior:

interface NestedColors {
  red: string;
  green: string;
  blue: string;
  more: {
    yellow: string;
    violet: string;
    black: string;
  }
}

// okay    
const activeNestedColors: NestedFlags<NestedColors> = {
  red: true,
  blue: false,
  green: true,
  more: {
    yellow: false,
    violet: true,
    black: true
  }
}

// error
const badActiveNestedColors: NestedFlags<NestedColors> = {
  red: true,
  blue: false,
  green: true,
  more: false
} 
// Types of property 'more' are incompatible.
// Type 'boolean' is not assignable to ...

TypeScript complains about badActiveNestedColors, saying that more should not be a boolean.

Hope that helps. Good luck!

like image 102
jcalz Avatar answered Oct 16 '22 01:10

jcalz