A guide to strictly defining the subclass type of object values in TypeScript

How do I enforce strict subclass typing for object values in the SHAPE_PARAMS definition? When using type assertion like <CircleParameter>, missing properties are not caught by linting.

Is there a way to define subclass types strictly?

const Shapes = {
  CIRCLE_A: "CIRCLE_A",
  CIRCLE_B: "CIRCLE_B",
  RECTANGLE_A: "RECTANGLE_A",
} as const;
type Shapes = typeof Shapes[keyof typeof Shapes];

interface ShapeParameter {
  color: string;
}

interface CircleParameter extends ShapeParameter {
  radius: number;
}

interface RectangleParameter extends ShapeParameter {
  width: number;
  height: number;
}

const SHAPE_PARAMS: { [key in Shapes]: ShapeParameter } = {
  [Shapes.CIRCLE_A]: <CircleParameter>{
    color: "red",
    radius: 1,
  },
  [Shapes.CIRCLE_B]: <CircleParameter>{
    // color: "blue", <- Missed property cannot be linted.
    radius: 2,
  },
  [Shapes.RECTANGLE_A]: <RectangleParameter>{
    color: "green",
    width: 3,
    // height: 3, <- Missed property cannot be linted.
  },
};

Answer №1

The issue at hand is that using the <TypeName> syntax asserts the object's type, instead of having the compiler validate it.

To address this, a more precise approach would involve modifying the type to be specific like so:

type ShapeParameterMap = {
  [Shapes.CIRCLE_A]: CircleParameter,
  [Shapes.CIRCLE_B]: CircleParameter,
  [Shapes.RECTANGLE_A]: RectangleParameter,
}

const SHAPE_PARAMS: ShapeParameterMap = {
  // ...
};

Playground Link

This adjustment allows for a more accurate type retrieval when accessing SHAPE_PARAMS[k] with a specific type of k.

Alternatively, if the repetition in the code seems excessive, you can achieve the desired outcome by creating a simple utility function that corroborates the type of its argument:

function check<T>(arg: T): T {
  return arg;
}

const SHAPE_PARAMS: Record<Shapes, ShapeParameter> = {
  [Shapes.CIRCLE_A]: check<CircleParameter>({
    color: "red",
    radius: 1,
  }),
  [Shapes.CIRCLE_B]: check<CircleParameter>({
    radius: 2,
    // missing property 'color'
  }),
  [Shapes.RECTANGLE_A]: check<RectangleParameter>({
    color: "green",
    width: 3,
    // missing property 'height'
  }),
};

Playground Link

Answer №2

One could determine the expected shape parameter type based on the key prefix:

type ShapeParams = {
  [K in Shapes]: K extends `CIRCLE_${string}` ? CircleParameter : RectangleParameter
}

/**  type ShapeParams is 
  {
    CIRCLE_A: CircleParameter;
    CIRCLE_B: CircleParameter;
    RECTANGLE_A: RectangleParameter;
  }
*/

const SHAPE_PARAMS: ShapeParams = {
  [Shapes.CIRCLE_A]: {
    color: "red",
    radius: 1,
  },
  [Shapes.CIRCLE_B]: { // <- Property 'color' is missing
    // color: "blue",
    radius: 2,
  },
  [Shapes.RECTANGLE_A]: { // <- Property 'height' is missing
    color: "green",
    width: 3,
    // height: 3,
  },
};

Playground

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

Creating trendy designs with styled components: A guide to styling functional components as children within styled parent components

I am looking to enhance the style of a FC styled element as a child inside another styled element. Check out the sandbox example here const ColorTextContainer = styled.div` font-weight: bold; ${RedBackgroundDiv} { color: white; } `; This resul ...

The element 'x' is implicitly bound with a type of 'any'

I've been exploring the world of Nextjs and TypeScript in an attempt to create a Navbar based on a tutorial I found (). Although I've managed to get the menu items working locally and have implemented the underline animation that follows the mou ...

Using TypeScript to map over unboxed conditions: transforming OR operators into AND operators

I am working with an array that has multiple objects containing functions foo. My goal is to create a new object signature with a function foo that inherits all the signatures from the array item foo functions. let arr = [ { foo: (a: 'a') = ...

Guide on implementing conditional return types in React Query

In my approach, I have a method that dynamically uses either useQuery or useMutation based on the HTTP method passed as a prop. However, the return type of this method contains 'QueryObserverRefetchErrorResult<any, Error>', which lacks meth ...

The NestJS error code TS7016 was triggered due to the absence of a declaration file for the 'rxjs' module

TS7016: An error occurred while trying to locate a declaration file for the module 'rxjs'. The file 'C:/Path/to/project/node_modules/rxjs/dist/cjs/index.js' is implicitly set to type 'any'. You can try running npm i --save-dev ...

Can I integrate @types/pkg into my custom library to automatically have types included?

Imagine I am creating a library that utilizes types from the Definitely Typed repository (such as @types/pkg). Would it be feasible for my package to automatically include these types when installed, eliminating the need for consumers to separately instal ...

We could not locate the export in Typescript, and the JSX element type does not show any construct or call signatures

Looking to utilize my Typescript React Component Library as a Package. The structure of my files is as follows: MyComponent.tsx: import React, { FunctionComponent } from 'react'; import styled from 'styled-components'; export interf ...

Utilizing Functions in Next.js with TypeScript: A Guide to Reusability

It is considered a best practice to separate data fetching functions into a folder named services, but I'm having trouble implementing this in my Next.js project. The function works when placed inside the component where I want to render the data, but ...

typescript undefined subscription to observable

After making an http request to fetch some data, I am facing issues in displaying it as intended. The dropdown select for entriesPerPage, and the left and right cursors for switching page in pagination are working fine. However, upon switching a page, I en ...

Instead of leaving an Enum value as undefined, using a NONE value provides a more explicit

I've noticed this pattern in code a few times and it's got me thinking. When you check for undefined in a typescript enum, it can lead to unexpected behavior like the example below. enum DoSomething { VALUE1, VALUE2, VALUE3, } f ...

Exploring the distinction between "() => void" and "() => {}" in programming

Exploring TS types, I defined the following: type type1 = () => {} type type2 = () => void Then, I created variables using these types: const customType1: type1 = () => { } const customType2: type2 = () => { } The issue surfaced as "Type ...

What is the process for specifying the type for the createApp(App) function in Vue.js 3?

I'm currently working with Vue3 and Firebase using TypeScript. // main.ts import { createApp } from 'vue' import App from './App.vue' import './registerServiceWorker' import router from './router' import store f ...

Is there a deeper philosophical rationale behind choosing to use (or not use) enums in TypeScript, along with string union types?

Recently, I delved into the world of enum and const enum in Typescript, causing some confusion. I grasped that const enum gets transpiled into simple values while regular enums do not. I also recognized certain distinctions between using string union type ...

Transforming JSON in Node.js based on JSON key

I am having trouble transforming the JSON result below into a filtered format. const result = [ { id: 'e7a51e2a-384c-41ea-960c-bcd00c797629', type: 'Interstitial (320x480)', country: 'ABC', enabled: true, ...

Angular is set up to showcase every single image that is stored within an array

I am having trouble displaying the images from the "image_url" array using a for loop. The images are not showing up as expected. Here is the content of the array: image_url: [ 0: "https://xyz/16183424594601618342458.5021539.jpg" 1: "https://xyz/1618342459 ...

Utilizing the componentDidUpdate method to update the state within the component

I have a scenario where two components are enclosed in a container. One component is retrieving the Id of a publication, while the other is fetching the issue date related to that specific publicationId. When I retrieve an Id, let’s say 100, it successf ...

"Utilizing the range option in a NodeJS request proves ineffective when attempting to stream HTML5

I have set up a nodejs request to serve videos with range support. The backend code looks like this: import { createReadStream, statSync } from 'fs'; const stats = statSync(path); const range = request.headers.range; const parts = ra ...

Web performance issues noticed with Angular 8 and Webpack 3.7 rendering speed

My web application is currently taking 35 seconds to render or at least 1.15 seconds from the initial Webpack start. I've made efforts to optimize Webpack, which has improved the launch speed, but the majority of time is consumed after loading main.j ...

What could be causing an error with NextJS's getStaticPaths when running next build?

When attempting to use Next.js's SSG with getStaticPaths and getStaticProps, everything worked fine in development. However, upon running the build command, an error was thrown: A required parameter (id) was not provided as a string in getStaticPath ...

When there are multiple tabs open in the browser, I notice a difference in the time displayed. This occurs in an Angular 2 environment

https://i.sstatic.net/l4YQ1.pngAfter a successful login, I am fetching server time from the back-end (in Java) and adding 1 second at intervals. Observable.interval(1000).map(() => { return this.time.add(1, 'seconds'); }). ...