Home > OS >  Typescript: Only type object values and let typescript type the keys
Typescript: Only type object values and let typescript type the keys

Time:09-28

I have an object like

interface PageDef = {url: string}
const page1Def: PageDef = {url: "page1"}
const page2Def: PageDef = {url: "page2"}
const pages = {page1: page1Def, page2: page2Def}

Now I would like the pages type to be the following:

{{page1: PageDef, page2: PageDef}}

The issue is that page could have any name, for instance I could have

const pages = {home: page1Def, about: page2Def}

I do not want to use any of the following, as I would loose the keys in the type

const pages: {[pageName: string]: PageDef} = {page1: page1Def, page2: page2Def}
const pages: Record<string, PageDef> = {page1: page1Def, page2: page2Def}

I wouldnt be able to do something like the following to have a union type of page name

keyof typeof pages
// would be string with above definition

Not typing would have Typescript to dynamically type pages, which would partially solve my problem but would type check the values.

So my question is, is it possible to manually type only the value of an object? Or is there any other solution?

CodePudding user response:

Do you mean something like this?

interface PageDef {url: string}
const page1Def: PageDef = {url: "page1"}
const page2Def: PageDef = {url: "page2"}

type pageNames = 'page1' | 'page2' | 'home' | 'about'

const pages: Partial<Record<pageNames, PageDef>> = {page1: page1Def, page2: page2Def}

CodePudding user response:

Why define a type for "pages" explicitly in the first place? It does not seem useful in this situation.

The reason to define a type would be so you could re-use it across multiple files, functions, or whatever. But there is no way to do that while keeping the keys of "pages" statically know across all usages, it would have to be string. Or, you could make it statically known by defining union type for it (like mentioned in the other answer, type pageNames = 'page1' | 'page2' | 'home' | 'about').

Like, what is your use case for explicitly typing "pages"? If you wanted to pass that value to a function, you would just use a generic anyway, the exact keys of "pages" would be irrelevant:

function doSomething<MyType extends {[key: string]: PageDef}>(pageObj: MyType) {}

Even then it would only be useful if the return from doSomething() was somehow usefully typed from its inferred use via generics, or you had other parameters on the function that were usefully informed via the generics. Otherwise, the body of the function probably doesn't actually care about the type of the keys of pageObj.

The only place it might be useful to explicitly know what the keys of "pages" are would be in the same scope that it was defined. And in that case, you can just let TypeScript infer its key values, which will be the actual keys instead of string:

interface PageDef = {url: string}
const page1Def: PageDef = {url: "page1"}
const page2Def: PageDef = {url: "page2"}
const pages = {page1: page1Def, page2: page2Def}

type PagesKeys = keyof typeof pages; // "page1" | "page2"

But again, why would you bother? Your requirements are a bit fuzzy of what you actually need this for. It might seem like it would be useful but I doubt it actually is. Or if it is, it seems like a design antipattern and you need to reconsider some other high level things in your design.

  • Related