Home > database >  How to say "one of the values of this readonly record" in typescript?
How to say "one of the values of this readonly record" in typescript?

Time:10-17

I have this enum

enum MyTypes {
 TypeA = 'TypeA';
 TypeB = 'TypeB';
}

and with that enum, I build this record:

const MyRecord: Readonly<Record<MyTypes, string>> = {
  [MyTypes.TypeA]: 'end-point-a',
  [MyTypes.TypeB]: 'end-point-b'
} as const

Now, I want to build a function that returns 'end-point-a' or 'end-point-b' so I can use the returned value on a switch like

const myEndpoint = getEndpoint();

switch(myEndpoint){
  case MyRecord.MyTypeA:
  ///
   break;
  case MyRecord.MyTypeB:
  ///
   break;
  default:
     thisFailsWithOtherStuffThanNever(myEndpoint) // This should not fail
}

But I can not figure out how to define "a value inside MyRecord". Is this even possible?

Playground

CodePudding user response:

You are not allowed yo use explicit Readonly<...> type with as const assertion. You need to use satisfies instead. This is new keyword which was introduced in TS 4.9

const MyRecord = {
  [MyTypes.TypeA]: 'end-point-a',
  [MyTypes.TypeB]: 'end-point-b'
} as const satisfies Readonly<Record<MyTypes, string>>


Now, you have a guarantee that MyRecord satisfies your type and you have type inference.

Now, you need to create a type which will consist of values of MyRecord obj. Let's name it Endpoints:

enum MyTypes {
  TypeA = 'TypeA',
  TypeB = 'TypeB',

}

const MyRecord = {
  [MyTypes.TypeA]: 'end-point-a',
  [MyTypes.TypeB]: 'end-point-b'
} as const satisfies Readonly<Record<MyTypes, string>>

type Values<T> = T[keyof T]

type EndpointMap = typeof MyRecord

type Endpoints = Values<EndpointMap>

The whole code:

enum MyTypes {
  TypeA = 'TypeA',
  TypeB = 'TypeB',

}

const MyRecord = {
  [MyTypes.TypeA]: 'end-point-a',
  [MyTypes.TypeB]: 'end-point-b'
} as const satisfies Readonly<Record<MyTypes, string>>

type Values<T> = T[keyof T]

type EndpointMap = typeof MyRecord

type Endpoints = Values<EndpointMap>


function thisFailsWithOtherStuffThanNever(nope: never) {
}

function getEndpoint(): Endpoints {
  return MyRecord.TypeA;
}

const myEndpoint = getEndpoint();

const foo = (endpoint: Endpoints) => {
  switch (endpoint) {
    case MyRecord.TypeA:
      ///
      break;
    case MyRecord.TypeB:
      ///
      break;
    default:
      thisFailsWithOtherStuffThanNever(endpoint)
  }
}

Playground

CodePudding user response:

You have defined the mapping login in two places:

First place is the MyRecord

const MyRecord: Readonly<Record<MyTypes, string>> = {
  [MyTypes.TypeA]: 'end-point-a',
  [MyTypes.TypeB]: 'end-point-b'
} as const

The second one is in the swithc/case statement.

const myEndpoint = getEndpoint();

switch(myEndpoint){
  ///..
}

You have to choose one. I'll propose two solutions to it. I prefer the first one. Playground links proived

Solution one: use that mapping object. Playground here


const endpointMapping: Readonly<Record<string, string>> = {
  [MyTypes.TypeA]: 'end-point-a',
  [MyTypes.TypeB]: 'end-point-b'
} as const

const myEndpoint = getEndpoint();

const mappedEndpoingValue = endpointMapping[myEndpoint];

if (mappedEndpoingValue !== undefined) {
   console.log(`I have foudn the mapping it is ${mappedEndpoingValue}`)
} else {
   // Handle it. Probably throw an error 
   console.error(`Not supportend endpoint type ${myEndpoint}`);
}

Solution two: use the switch/case statement. Pllaygroudn using switch case

const myEndpoint = getEndpoint();

let mappedEndpoingValue: string = "";

switch (myEndpoint) {
  case MyTypes.TypeA:
    mappedEndpoingValue = "end-point-a"
    break;
  case MyTypes.TypeB:
    mappedEndpoingValue = "end-point-a"
    break;
  default: 
   // Handle it. Probably throw an error 
   console.error(`Not supportend endpoint type ${myEndpoint}`);
   throw new Error("not fount mapping")
}

console.log(`I have foudn the mapping it is ${mappedEndpoingValue}`)
  • Related