Creating a TypeScript type or interface that represents an object with one of many keys or simply a string

I am tasked with creating an interface that can either be a string or an object with one of three specific keys.

The function I have takes care of different errors and returns the appropriate message:

export const determineError = (error: ServerAlerts): AlertError => {
  if (typeof error !== "string") {
    if (error.hasOwnProperty("non_field_errors")) {
      return error.non_field_errors[0];
    } else if (error.hasOwnProperty("detail")) {
      return error.detail;
    } else if (error.hasOwnProperty("email")) {
      return error.email[0];
    } else {
      return UNKNOWN_ERROR;
    }
  } else {
    return error;
  }
};

These are the defined types:

export type AlertError =
  | "Unable to log in with provided credentials."
  | "E-mail is not verified."
  | "Password reset e-mail has been sent."
  | "Verification e-mail sent."
  | "A user is already registered with this e-mail address."
  | "Facebook Log In is cancelled."
  | string;

export interface ServerAlerts {
  non_field_errors: [string];
  detail: string;
  email: [string];
}

However, my current design for ServerAlerts doesn't fully meet my needs. It should also accommodate cases where ServerAlerts could be just a plain string, and when it has one key, it only contains one value.

How would you suggest redesigning the type or interface to handle these scenarios?

EDIT: I attempted to make the keys optional by adding question marks, but then my linter raises concerns in the error return statements of determineError.

Answer №1

If my comprehension is correct, you simply need to declare the parameter as either ServerAlerts or string:

export const validateError = (error: ServerAlerts|string): AlertError => {
// -----------------------------------------------^^^^^^^

You mentioned in a comment that all three properties of ServerAlerts are optional, so make sure to mark them as such using ?:

interface ServerAlerts {
  non_field_errors?: [string];
  detail?: string;
  email?: [string];
}

This means that any object typed as object will also be valid, since all fields are optional. Combined with the above change, here's what you'll get:

validateError("foo");                       // Works
validateError({ non_field_errors: ["x"] }); // Works
validateError({ detail: "x" });             // Works
validateError({ email: ["x"] });            // Works
validateError({});                          // Works (due to all fields being optional)
let nonLiteralErrors: object;
nonLiteralErrors = { foo: ["x"] };
validateError(nonLiteralErrors);      // Works (due to all fields being optional)
validateError({ foo: ["x"] });              // Fails (correctly)

Playground example

Suggestions have been made to use object in the parameter signature. If one of the three fields should be required (removing the need for the UNKNOWN_ERROR branch), you can define three interfaces and create a union type with them inside ServerAlerts:

interface ServerAlertsNonFieldErrors {
  non_field_errors: [string];
}

interface ServerAlertsDetail {
  detail: string;
}

interface ServerAlertsEmail {
  email: [string];
}

type ServerAlerts = ServerAlertsNonFieldErrors | ServerAlertsDetail | ServerAlertsEmail;

You would then use type assertions when returning the specific field:

if (error.hasOwnProperty("non_field_errors")) {
  return (error as ServerAlertsNonFieldErrors).non_field_errors[0];
// ------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

With this approach, you'd have:

validateError("foo");                       // Works
validateError({ non_field_errors: ["x"] }); // Works
validateError({ detail: "x" });             // Works
validateError({ email: ["x"] });            // Works
validateError({});                          // Fails (correctly)
let nonLiteralErrors: object;
nonLiteralErrors = { foo: ["x"] };
validateError(nonLiteralErrors);      // Fails (correctly)
validateError({ foo: ["x"] });              // Fails (correctly)

Playground Example

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

Using Angular 4 to monitor changes in two-way binding properties

Recently, I developed a unique toggle-sorting component that examines if the current sorting parameters align with its sorting slug and manages clicks to reflect any changes. // toggle-sorting.component.ts @Input() sortingSlug: string; @Input() currSorti ...

What could be causing my TypeScript project to only fail in VScode?

After taking a several-week break from my TypeScript-based open-source project, I have returned to fix a bug. However, when running the project in VScode, it suddenly fails and presents legitimate errors that need fixing. What's puzzling is why these ...

When trying to access a certain class property, I was met with the following error message: TypeError: Unable to read/set property 'x' of

Lately, I've delved into the realm of JavaScript / TypeScript and decided to create a basic React App using TypeScript. Within one of my components, I aim to switch between different components using a "state" (where each component will follow the pre ...

Angular - Automatically update array list once a new object is added

Currently, I'm exploring ways to automatically update the ngFor list when a new object is added to the array. Here's what I have so far: component.html export class HomePage implements OnInit { collections: Collection[]; public show = t ...

Should the null-forgiving operator be avoided when using `useRef`?

Is the following code snippet considered poor practice? const Component: React.FC<{}> = () => { const ref = React.useRef<HTMLDivElement>(null!); return <div ref={ref} />; } I'm specifically questioning the utilization of ...

What is the best way to incorporate ControlContainer in an Angular component's unit test?

Is there a way to include ControlContainer in an Angular unit test? The error message I am encountering reads: NullInjectorError: StaticInjectorError(DynamicTestModule)[ChildFormComponent -> ControlContainer]: StaticInjectorError(Platform: core) ...

I'm struggling to find the right Typescript syntax for defining a thunk function that returns a value while using React Redux Toolkit

Currently, I am utilizing TypeScript within a React Redux Toolkit project. While attempting to create an Async Thunk action function that is expected to return a boolean value, I found myself struggling with determining the correct TypeScript syntax: expor ...

Examining Axios HttpService piping through a NestJS middleware in a unit test

A middleware function retrieves a JSON document from a microservice endpoint and appends it to the request. The good path test is successful, but I'm struggling to make the bad path test throw a ForbiddenException and stop it from invoking next(). W ...

Angular threw an error saying: "Template parse errors: is not a recognized element"

I am attempting to utilize babel standalone within a react application to transpile Angular TypeScript. The transpiling process seems to be successful, however, I encounter an error when trying to import a component and use its selector within the template ...

Issue with rendering HTML tags when replacing strings within Ionic 2 and Angular 2

I am currently working with an array of content in my JSON that includes URLs as plain text. My goal is to detect these text URLs and convert them into actual clickable links. However, I'm facing an issue where even though the URL is properly replaced ...

When utilizing a generic type with a class, type T fails to meet the specified constraint

export type ExtractType<T extends Array<{ name: Array<string>, type: keyof TypeMapping }>> = { [K in T[number]['name'][0]]: TypeMapping[Extract<T[number], { name: K }>['type']] } export class CommandLineParse ...

Allow for an optional second parameter in Typescript type definition

Here are two very similar types that I have: import { VariantProps } from "@stitches/core"; export type VariantOption< Component extends { [key: symbol | string]: any }, VariantName extends keyof VariantProps<Component> > = Extra ...

Creating a canvas that adjusts proportionally to different screen sizes

Currently, I am developing a pong game using Angular for the frontend, and the game is displayed inside an HTML canvas. Check out the HTML code below: <div style="height: 70%; width: 70%;" align="center"> <canvas id=&q ...

Execute supplementary build scripts during the angular build process

I've developed an Angular application that loads an iframe containing a basic html page (iframe.html) and a Vanilla JavaScript file (iframe.js). To facilitate this, I've placed these 2 files in the assets folder so that they are automatically cop ...

react-i18next: issues with translating strings

I encountered a frustrating issue with the react-i18next library. Despite my efforts, I was unable to successfully translate the strings in my application. The relevant code looked like this: App.tsx: import i18n from 'i18next'; import { initR ...

What allows the execution of "T[keyof T] extends Function" in TypeScript specifically for Strings?

Lately, I've been experimenting with type changes and I find myself puzzled when encountering code like the following: type DeepReadonly<T> = { readonly [k in keyof T]: T[k] extends Function?T[k]:DeepReadonly<T[k]> } // Let's defin ...

Angular API snapshot error: The type 'IJobs' does not match the expected type 'IJobs[]'

Currently, I am in the process of learning and attempting to construct a job board using Angular 10. Although my API setup seems to be functioning properly, when navigating to the job detail page on Chrome, an error is displayed: ERROR in src/app/job-det ...

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 ...

Guide: Implementing material-ui theme with redux in gatsby

I'm currently utilizing the material-ui theme in conjunction with redux-toolkit within a gatsby project. This is my theme.ts file: import { createMuiTheme } from "@material-ui/core"; import { useSelector } from "react-redux"; import { State } from ". ...

Troubleshooting TypeScript errors in Cassini with Google Chrome

While troubleshooting a .NET 4.6.1 web application with Cassini in Visual Studio 2015 version 14, update 3, I encountered an error on a page that utilizes TypeScript: Refused to execute script from 'http://localhost:53049/Scripts/app.ts' because ...