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

Custom component not rendering expected CSS style

I have successfully developed a custom web component without using any framework. I then proceeded to populate it with content from a template tag. Although I was able to manipulate the content using JavaScript, I encountered difficulties when trying to m ...

Accessing element from view within controller in Ionic version 3.5

I am currently working on a project in Ionic 3.5, where I need to implement a feature that automatically loads an image "ad" after the page finishes loading. Right now, clicking a button successfully displays the image. However, I want this functionality ...

The challenge of handling Set type in TypeScript errors

I'm currently facing two errors while trying to convert a function to TypeScript. The issue lies with the parameters, which are of type Set import type {Set} from 'typescript' function union<T>(setA: Set<T>, setB: Set<T>) ...

Utilize the useState() hook to add an object and display its data in a React Native

Here is a function I am working with: const [dataLoc, setDataLoc] = useState({date: "No data received yet from sensor", coords: {}}); This is where I set the location: Geolocation.getCurrentPosition( location => { const date = d ...

The error message "TypeError: this.subQuery is not a function" is displayed

Whenever I execute the tests using jest, I consistently encounter the error message TypeError: this.subQuery is not a function pointing to a specific line in the testModelDb.test.ts file. In the tests/jest.setup.ts file: import 'reflect-metadata&apos ...

Typescript interface created specifically for React Higher Order Component Props

Consider the React HOC provided below which adds sorting state to a component: import React, {Component, ComponentClass, ComponentType} from 'react' interface WithSortState { sortOrder: string } interface WithSortInjectedProps { sortO ...

The attribute is not found on the combined type

After combing through various questions on stackoverflow, I couldn't find a solution to my specific case. This is the scenario: interface FruitBox { name: string desc: { 'orange': number; 'banana': number; } } interf ...

Is it possible to enhance an interface by integrating the characteristics of a constant?

I am currently working on customizing a material-ui v4 Theme. Within our separate @our-project/ui package, we have the following: export declare const themeOptions: { palette: { // some colors missing from Palette } status: string; // other pro ...

Designing a personalized mat-icon using the Github SVG

Trying to create a unique custom SVG mat-icon by loading the SVG directly from Github. My initial attempt using DomSanitizer was documented in this article, and it successfully loaded the SVG via HttpClient. Now, I am attempting to achieve this directly t ...

Exploring the World of Geometry with Three.js and TypeScript

How can I correctly define types for Mesh Vertices and Faces? In my initial attempt, I decided to create a new Mesh object. However, when attempting to access Vertices and Faces from the geometry property, I encountered a few errors: const material = new ...

Trouble encountered when using RxJS zip and pipe together

In my Angular Resolver, I am facing a scenario where I need to wait for two server calls. The catch is that the second server call is optional and can be skipped based on user input. This data retrieval process is crucial for loading the next page seamless ...

Ways to use DecimalPipe for rounding numbers in Angular - Up or down, your choice!

Looking to round a number up or down using DecimalPipe in Angular? By default, DecimalPipe rounds a number like this: Rounding({{value | number:'1.0-2'}}) 1.234 => 1.23 1.235 => 1.24 But what if you want to specifically round up or do ...

Solving the problem of cookieParser implementation in NestJS

Greetings! I have a question for you. Currently, I am working on developing a backend using NestJS, a Node.js framework. Everything is functioning smoothly except for some issues when it comes to hosting. I have created a new NestJS project and made some ...

Issue with TypeScript: Declaring type for objects in an array using .map

I'm having trouble assigning types to the Item object that is being returned from unitedStates.map((item: Item) => {}. Despite my efforts, I am unable to correctly define the types. Although I have specified the unitedStates array of objects as un ...

Incorporate an external library

I am currently facing a challenge in my angular2 project where I need to import a 3rd party library. Here are the steps I have taken so far: ng new myproject npm install --save createjs-easeljs npm install @types/easeljs However, I am stuck at this poin ...

Typescript implementation for a website featuring a single overarching file alongside separate files for each individual webpage

Although I've never ventured into the realm of Typescript before, I am intrigued by its concept of "stricter JS". My knowledge on the subject is currently very limited as I am just starting to experiment with it. Essentially, I have developed my own ...

React - retrieving the previous page's path after clicking the browser's "back" button

Imagine I'm on Page X(/path-x) and then navigate to page Y(/path-y). Later, when I click the "back" button in the browser. So my question is, how do I retrieve the value of /path-y in PageX.tsx? Note: I am utilizing react-router-dom ...

Properly incorporating a git+https dependency

I'm facing an issue while trying to utilize a git+https dependency from Github to create a TypeScript library. I've minimized it to a single file for illustration purposes, but it still doesn't work. Interestingly, using a file dependency fu ...

Enhance your Angular application with lazy loading and nested children components using named outlets

Let me start by explaining that the example provided below is a simplified version of my routes that are not functioning properly. I am working on an angular project, specifically a nativescript angular project, and I suspect the issue lies within Angular ...

What is the best way to implement asynchronous guarding for users?

Seeking assistance with implementing async route guard. I have a service that handles user authentication: @Injectable() export class GlobalVarsService { private isAgreeOk = new BehaviorSubject(false); constructor() { }; getAgreeState(): Obser ...