TypeScript's exhaustiveness check seems to be malfunctioning

Imagine we are developing a DB model for the entity Post. Since the database stores data as strings, we need to create a parse function that can take a raw database object and convert it into the correct Post interface.

To replicate this, enable the noImplicitReturns: true setting.

interface Post {
  id:   number
  text: string
}

function parse<K extends keyof Post>(k: K, v: any): Post[K] {
  switch(k) {
    case 'id':   return parseInt(v)
    case 'text': return v.toString()
  }
}

This code has two errors. First, it will not compile because TypeScript requires a default statement in the switch block. Secondly, it may not detect when you are checking against the wrong value. The incorrect code below would compile without errors:

function parse<K extends keyof Post>(k: K, v: any): Post[K] {
  switch(k) {
    case 'id':   return parseInt(v)
    case 'text': return v.toString()
    case 'some': return v.toString() // <= error, no `some` key
    default: return ''               // <= this line is not needed
  }
}

There is even a third error where TypeScript allows returning an incorrect value for the key. Consider the following faulty code:

function parse<K extends keyof Post>(k: K, v: any): Post[K] {
  switch(k) {
    case 'id':   return parseInt(v)
    case 'text': return 2 // <= error, it should be string, not number
    default: return ''
  }
}

Are these limitations of TypeScript or have I made a mistake in my implementation?

Answer №1

To start, it's important to note that when K extends keyof Post, it doesn't mean that K is exactly equal to keyof Post. To limit the potential values of K, it's necessary to use keyof Post instead of a generic type like K.

Furthermore, if you encounter issues related to default options being enforced even when not desired, ensure that the setting "switch-default": true in your tsconfig.json file is adjusted to false.

Lastly, keep in mind that accessing Post[K] or

Post[keyof Post]</code will encompass all possible types of properties within <code>Post
, including the possibility of number. TypeScript does not automatically restrict Post[K] to the type of its corresponding K property unless specified otherwise. One approach to address this is by defining a mapping type, such as
A = ['id', numer] | [text', string]
, and then defining Post using [A[0]]: A[1]

Hopefully, these insights prove helpful!

Answer №2

Do you find this alternative approach appealing?

interface Article {
  id:   number
  content: string
}

type Parsers = {
    [k in keyof Article]: (value: any) => Article[k]
}

const parsers: Parsers = {
    id (value: any) {
        return parseInt(value)
    },
    content (value: any) {
        return value.toString()
    }
}

parsers.id('123') // number
parsers.content({}) // string

// Moreover, introducing additional parsers or returning an incorrect value type from a parser is checked for validity.

Update

By making the type declaration of Parser (renamed from Parsers) generic and utilizing arrow functions and contextual type inference, the code can be enhanced as follows:

type Parser<T> = {
    [k in keyof T]: (value: any) => T[k]
}

interface Article {
  id:   number
  content: string
}

const parser: Parser<Article> = {
    id: value => parseInt(value),
    content: value => value.toString()
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Discovering the index of an item in Angular

My delete function emits socket.io to update the other party's items list and remove the specific item. The issue arises when I receive the socket data as I struggle to find the matching item to update it. Logic User 1 deletes a message User 2 receiv ...

Is there a way to include the request body (req.body) in the msg object using express-winston?

My current challenge involves logging {{ req.body }} using the msg: object in express-winston. Even after whitelisting the body with expressWinston.requestWhitelist.push('body');, it still does not appear in the log. export const accessLogger = ...

Unable to implement multiple draggable inner objects using Angular 5 and dragula library

After struggling for the past few days, I can't seem to get it to work... Here is a brief explanation of my issue: In this example, I have an array of objects structured like this: public containers: Array<object> = [ { "name": "contain ...

Is there a way to perform type narrowing within an Angular template?

I'm facing an issue with a component that requires a business object as an Input. Within the template of this component, I need to conditionally display some content based on the presence of a property that only exists in certain subclasses of the bus ...

Encountering a 'Duplicate identifier' issue while trying to transition project to Typescript

Currently in the process of transitioning an existing JavaScript application to TypeScript. To facilitate a gradual conversion, I began by utilizing the "allowJs" compiler option to compile the original JavaScript code. However, as I start converting files ...

Guide on properly documenting custom function types in JSDoc or TypeScript to ensure accurate referencing for VSCode IntelliSense functionality

I am currently working on documenting custom function types within an object and would greatly appreciate any assistance: A Closer Look at the Issue Consider this basic object declaration with several function properties (addCoordinate, addCoordinateOne, ...

Tips for creating an operation within a JSON document?

Today, I am attempting to customize the appearance of my audiobook list. However, when trying to add an aspectRatio key-value pair to each object in my JSON file, I encountered an error. https://i.stack.imgur.com/Qb3TX.png https://i.stack.imgur.com/qTkmx. ...

Struggling to access my lambda function, an unfamiliar error from AWS/Serverless has presented itself

I'm currently in the process of developing a nodejs Lambda API using serverless. However, once it's deployed and I attempt to access my API endpoints, the server is throwing back an internal error. Unfortunately, CloudWatch isn't providing m ...

Are my Angular CLI animations not working due to a version compatibility issue?

I've been working on a project that had Angular set up when I started. However, the animations are not functioning correctly. The mat input placeholder doesn't disappear when typing, and the mat-select drop-down is not working. Here is my packag ...

What is the best way to implement global error handling for NextJS API requests?

Is there a method to set up a Global error handler that captures the stack trace of errors and sends it to an external system like NewRelic without needing to modify each individual API? This would follow the DRY principle by avoiding changes to multiple ...

Angular end-to-end testing doesn't locate the tag until the timeout expires following a route change

Recently, I've been diving into the world of e2e testing. So far, everything has been going smoothly with my tests on the first page - checking the title, h1 tag text, and number of cards. The issue arises when I try to navigate to a second page using ...

Mongoose Error: Typescript String Error: 'not assignable to type 'string'

I need help with setting up a logout route in my express backend. The idea is that when a user logs out, their JWT token should be stored in a blacklist Mongo database to prevent reusing the same token for login. The issue I'm facing is an error rela ...

Ways to set a default value for a union type parameter without encountering the error "could be instantiated with a different subtype of constraint"

After referring to my recent inquiry... Can a default value be specified for valueProp in this scenario? type ValueType = 'value' | 'defaultValue' type Props<T extends ValueType> = Record<T, string> ...

Creating personalized mapping for TypeScript objects

I have a TypeScript object structure that resembles the following: { "obj1" : { object: type1;}; "obj2" : { object: type2;}; "obj3" : { object: type3;}; "obj4" : { object: type4;}; "obj5" ...

Dynamic routing with ngIf in Angular 2's router system

Is there a way to use *ngIf with dynamic router in Angular? Let's say I have a top navigation component with a back button, and I only want the back button to be visible on the route 'item/:id'. I tried using *ngIf="router.url == '/ite ...

What is the best way to utilize *ngSwitchWhen in a TypeScript environment?

I am currently working with Ionic2 and Angular2 and encountering an issue while trying to implement a segment using ngSwitchWhen. Unfortunately, the functionality is not working as expected and I am receiving an error message. How can I resolve this issue ...

I am encountering an issue where my application is not recognizing the angular material/dialog module. What steps can I take to resolve this issue and ensure that it functions properly?

For my Angular application, I am trying to incorporate two Material UI components - MatDialog and MatDialogConfig. However, it seems like there might be an issue with the placement of these modules as all other modules are functioning correctly except fo ...

Mapping type keys to camelCase in Typescript: a guide

As someone who is relatively new to typescript, I am eager to learn how to create a mapped type that converts keys from one type to another. Specifically, if I have a type where all the keys are written in snake case, how can I create a new type with camel ...

Refreshing an Angular page when navigating to a child route

I implemented lazy loading of modules in the following way: { path: 'main', data: {title: ' - '}, component: LandingComponent, resolve: { images: RouteResolverService }, children: [ { path: '', redirectTo: 'home&apo ...

Issue encountered with Azure DevOps during TypeScript (TS) build due to a type mismatch error: 'false' being unable to be assigned to type 'Date'. Conversely, the build functions correctly when run locally, despite the type being defined as 'Date | boolean'

I am facing an issue with my NestJS API while trying to build it using Azure DevOps pipeline. The build fails with the following error: src/auth/auth.controller.ts(49,7): error TS2322: Type 'false' is not assignable to type 'Date'. src/ ...