iceblueorbitz
iceblueorbitz

Reputation: 1060

Typescript validating all object values while extracting keys as union

I'm trying to validate an object literal's values, and at the same time create a union of all the keys as a parameter type to a function.

interface PathValue {
    prop1: string;
    prop2?: number;
}

interface PathDeclaration {
    [key: string]: PathValue;
}

const allPaths: PathDeclaration = {
    'one': {prop1: 'foo'},
    'two': {prop1: 'bar', prop2: 2},
}


function getItem(path: Extract<keyof typeof allPaths, string>) {
    return allPaths[path];
}

The problem with this example is that since PathDeclaration is the type of allPaths, and its keys are just generic strings, my path parameter can't infer the keys from the object literal anymore.

The only solutions I can think of is to have to declare all the keys in a separate interface (duplicate code), or instead of typing allPaths, I could remove PathDeclaration and just type each value like 'one': <PathValue> {prop1: 'foo'}, neither which are very elegant solutions. Is there a way to validate the entire allPaths object AND create a key union type from the object literal?

Upvotes: 0

Views: 131

Answers (1)

ford04
ford04

Reputation: 74500

You want to initialize const allPaths with a generic function to 1) add the type constraint PathDeclaration and 2) let TS infer the given object literal type automatically. Only functions can do this in one step.

const createPaths = <T extends PathDeclaration>(pd: T) => pd

const allPaths = createPaths({
  'one': { prop1: 'foo' },
  'two': { prop1: 'bar', prop2: 2 },
})
/* 
{
  one: { prop1: string; };
  two: { prop1: string; prop2: number; };
}
 */

Above will catch errors:

const allPathsError = createPaths({
  'one': { prop3: 'foo' }, // error
  'two': { prop1: 'bar', prop2: "bar" }, // error
})

And infer all keys:

// path: "one" | "two"
function getItem(path: Extract<keyof typeof allPathsInline, string>) {
  return allPathsInline[path];
}

This is also one of the few cases where I like to use an IIFE to make things lean:

const allPathsInline = (<T extends PathDeclaration>(pd: T) => pd)({
  'one': { prop1: 'foo' },
  'two': { prop1: 'bar', prop2: 2 },
})

Code sample

Upvotes: 2

Related Questions