Home > OS >  Use values of string array A as keys in object O when A and O are both properties of an object
Use values of string array A as keys in object O when A and O are both properties of an object

Time:10-01

I want to create a type that is an object, with properties values, and valuesMap. values should be an array of strings, and valuesMap should be an object whose keys are entries in values, and whose values are either a string or a boolean. I am struggling to define this type constraint in typescript. Here's what I've tried:

type FilterOptionBase<K extends string> = {
  name: string;
  values: K[];
  multiselect: boolean;
  isBoolean?: boolean;
  valuesMap?: {
    [key in K]: string | boolean;
  };
};

type FilterOption = FilterOptionBase<string>

const myBadFilter: FilterOption = {
    name: 'Status',
    values: ["Online", "Offline"],
    multiselect: false,
    valuesMap: {
        Online: true,
        NotSure: false // <------ should error but doesnt
    }
}

TypeScript playground

I feel like this should be relatively simple but the solution is escaping me. If this has been asked before, please direct me to the right place!

Also:

If isBoolean is defined as true, can we also contrain the values of valueMap to be a boolean, rather than a boolean | string?

CodePudding user response:

The reason your example isn't working is because this:

type FilterOption = FilterOptionBase<string>

...is passing string into FilterOptionBase, essentially doing this:

type FilterOptionBase = {
  name: string;
  values: string[];
  multiselect: boolean;
  isBoolean?: boolean;
  valuesMap?: {
    [key in string]: string | boolean;
  };
};

So to type it correctly, you'd need to pass in the list of strings you'd expect when using the type, like this:

const myBadFilter: FilterOptionBase<"Online" | "Offline"> = {
  name: 'Status',
  values: ["Online", "Offline"],
  multiselect: false,
  valuesMap: {
      Online: true,
      NotSure: false // <------ errors now!
  }
}

However, I think what you're actually trying to do is use the FilterOptionBase type as a template of sorts for all kinds of different objects, and the unfortunate part is that there's no way to do it that way. You can't have a type that is both reading from itself and constraining itself.

But you can access the values on an object that's passed into a function, so here's a workaround that I've used in the past to great effect:

const filterFactory = <K extends string>(v: FilterOptionBase<K>) => v

const myBadFilter = filterFactory({
  name: 'Status',
  values: ['Online', 'Offline'],
  multiselect: false,
  valuesMap: {
    Online: true, 
    NotSure: false, // <------ it's an error!
  }
})

"Also" Answer

Yes, it's possible to change it based on isBoolean being true. You'll just need to do a union:

type foo = {
  isBoolean: true
  value: boolean
} | {
  isBoolean: false
  value: string
}

Though it does get a little trickier because of the workaround you have to do with the factory function. Here it is:

type FilterOptionBase<
  K extends string,
  B extends boolean | undefined,
> = B extends true
  ? {
      name: string
      values: K[]
      multiselect: boolean
      isBoolean: B
      valuesMap?: {
        [key in K]: boolean
      }
    }
  : {
      name: string
      values: K[]
      multiselect: boolean
      isBoolean?: B
      valuesMap?: {
        [key in K]: string
      }
    }

const filterFactory = <K extends string, B extends boolean | undefined>(
  v: FilterOptionBase<K, B>,
) => v

const myBadFilter = filterFactory({
  name: "Status",
  values: ["Online", "Offline"],
  multiselect: false,
  isBoolean: true,
  valuesMap: {
    Online: true,
    Offline: "foo", // <------ also an error!
  },
})

const myOtherBadFilter = filterFactory({
  name: "Status",
  values: ["Online", "Offline"],
  multiselect: false,
  isBoolean: false,
  valuesMap: {
    Online: true, // <------ also an error!
    Offline: "foo",
  },
})

CodePudding user response:

How about explicitly passing the allowed values as type parameter when constructing FilterOption:

type FilterOption = FilterOptionBase<'Online' | 'Offline'>

Playground

  • Related