Is it possible to identify different types of mappings during runtime?

Greetings for the extensive explanation provided, as I am still in the learning process of typescript, I have tried my best to articulate my question effectively. Let me begin by explaining:

// introducing the base "Failure" type which serves as the foundation for all other "Failures":
class Failure { constructor (public message: string) {} }
type FailureResult<R> = R extends Failure ? R : never;

// anything that is not a Failure is considered a Success.
type SuccessResult<R> = R extends Failure ? never : R;

// the main goal here is to have a collection of functions within an object...
type MultipleChecks<C, R> = {
  [K in keyof R]: (context: C) => R[K]
}

// after executing all functions, the results are categorized into
// a type where all elements are some type of Failure (or null)...
type MappedFailure<R> = {
  [K in keyof R]: FailureResult<R[K]>|null
};
// ... or a type where all elements are successes:
type MappedSuccess<R> = {
  [K in keyof R]: SuccessResult<R[K]>
};

// though not absolutely necessary for the minimal reproduction, I am also
// consolidating those combined failures into a single failure:
class MultipleFailures<R> extends Failure {
  readonly failures: MappedFailure<R>;

  constructor (failures: MappedFailure<R>) {
    super('multiple failures');
    this.failures = failures;
  }
}

type MappedResult<R> = MappedSuccess<R>|MultipleFailures<R>;

// Everything up until this point behaves as expected. Now, here comes
// the part that is challenging to implement correctly:
function validateMultiple<C, R> (context: C, checks: MultipleChecks<C, R>) : MappedResult<R> {
  const results : MappedSuccess<R> = {} as MappedSuccess<R>
  const failures : MappedFailure<R> = {} as MappedFailure<R>
  let checkFailed = false;

  for (const key of Object.keys(checks) as [keyof MultipleChecks<C, R>]) {
    const fn = checks[key];
    const result = fn(context);
    if (result instanceof Failure) {
      checkFailed = true;
      // @ts-expect-error Type 'R[keyof R] & Failure' is not assignable
      // to type 'FailureResult<R[keyof R]>'.
      failures[key] = result;
    } else {
      // @ts-expect-error Type 'R[string]' is not assignable to type
      //'SuccessResult<R[keyof R]>'.
      results[key] = result;
      failures[key] = null;
    }
  }
  if (checkFailed) return new MultipleFailures(failures);
  else return results;
}

For better clarity, I have included an example implementation:

class NumberTooSmall extends Failure {
  constructor () { super('number too small.'); }
}

class NumberTooBig extends Failure {
  constructor () { super('number too big.'); }
}

type NumberResult = { a: number, b: number } | NumberTooSmall | NumberTooBig;

function validateNumbers ({a, b}: { a: number , b: number }) : NumberResult {
  if (a < 1 || b < 1) return new NumberTooSmall();
  if (a > 100 || b > 100) return new NumberTooBig();
  return { a, b };
};

class NameTooShort extends Failure {
  constructor () { super('name too short.'); }
}

class NameTooLong extends Failure {
  constructor () { super('name too long.'); }
}

type NameResult = string | NameTooShort | NameTooLong;

function validateName ({ name }: { name: string }) : NameResult {
  if (name.length < 2) return new NameTooShort();
  if (name.length > 5) return new NameTooLong();
  return name;
};

function addByName (context: { a: number, b: number, name: string }) : string {
  const validation = validateMultiple(context, {
    numbers: validateNumbers,
    name: validateName
  });

  if (validation instanceof MultipleFailures) {
    let message = 'Failed to add:'
    for (const f of Object.values(validation.failures)) {
      if (f !== null) message += `\n${f.message}`
    }
    return message;
  } else {
    const { name, numbers } = validation;
    const { a, b } = numbers;
    return `${name} added ${a} + ${b} and got ${a + b}.`;
  }
}

Explaining verbally, with the help of the top types FailureResult and SuccessResult, the function's return type is divided into failure and success, like so:

type FooContext = { fooInput: string }
type FooResult = Foo|FooErrorA|FooErrorB
function getFoo (context: FooContext) : FooResult;
type FooSuccess = SuccessResult<FooResult>; // i.e. Foo
type FooFailure = FailureResult<FooResult>; // i.e. FooErrorA|FooErrorB

Though the above segment works smoothly. The objective is to gather similar functions into an object, execute them sequentially, and then map the results into an object comprising of either all successes or all Failure|null. From a type perspective, this concept also seems feasible using the MappedSuccess<R> and MappedFailure<R> types:

type MultiContext = FooContext & BarContext;
type MultiChecks = { foo: FooResult, bar: BarResult };

// either all fields have no error value, or the outcomes are condensed into
// a failure object where each field represents the failure or null.
type AllSuccess = { foo: FooSuccess, bar: BarSuccess };
type SomeFailures = { foo: FooFailure|null, bar: BarFailure|null }
type MultiResult =  AllSuccess|{ failures: SomeFailures }
function validateMultiple(context: MultiContext, MultiChecks) : MultiResult;

The actual problem (as depicted in the sample implementation of validateMultiple) arises when TypeScript does not recognize the safety of the intermediate values generated while iterating over the objects. Although I am confident about their correctness informally, it seems like I may need to use some type assertions, yet I am struggling to determine what those would entail.

Answer №1

When looking at the implementation of ValidateMultiple, we see that the type of result is R[keyof R], which may or may not be narrowed to R[keyof R] & Failure. However, neither of these types is considered assignable to the type of failures[key] or results[k]. The FailureResult<XXX> and SuccessResult<XXX> types are conditional types that rely on unspecified type parameters like R, making them appear almost entirely opaque to the compiler. Due to this, the compiler cannot determine the specific value that could be assigned to results[key], resulting in the following error:

if (result instanceof Failure) {
  checkFailed = true;
  failures[key] = result; // error!
  // not assignable to type 'FailureResult<R[keyof R]> | null'.
} else {
  results[key] = result; // error!
  // not assignable to type 'SuccessResult<R[keyof R]>'.
  failures[key] = null;
}

If the goal is simply to suppress these errors without making major refactorings to convince the compiler of safety, then using a type assertion for each error is appropriate.

By interpreting the error messages, we can determine the appropriate assertions to use. In the first case, the error indicates that result cannot be assigned to

FailureResult<R[keyof R]> | null
, suggesting that in this situation, result should be assigned to FailureResult<R[keyof R]>. Similarly, in the second case where result is not assignable to SuccessResult<R[keyof T]>, we should assert that it is of type SuccessResult<R[keyof R]>:

if (result instanceof Failure) {
  checkFailed = true;
  failures[key] = result as FailureResult<R[keyof R]>; // okay
} else {
  results[key] = result as SuccessResult<R[keyof R]> ; // okay
  failures[key] = null;
}

These assertions eliminate the errors, as intended.

Access the playground link to view the code

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

There appears to be an issue with the dynamic functionality of RouterLink in Angular 6

user-dashboard.html <ul class="nav flex-column"> <li class="nav-item"> <a class="nav-link" routerLink='/dashboard'>User Dashboard</a> </li> <li class="nav-item" *ngFor="let cat of categories; let i = in ...

What is the correct method for importing React in TypeScript (.tsx) to ensure optimal integration?

Within our TSX/React web application, we employ two distinct import styles for the react module: import * as React from "react"; as well as import React from "react" As far as I know, both methods function without any noticeable differences. Is there a ...

Each Tab in Ionic2 can have its own unique side menu that opens when selected

In my ionic2 app, I wanted to implement a unique side menu for each of my tabs. Here is what I attempted: I used the command ionic start appname tabs --v2 to create the initial structure. Next, I decided to turn both home.html and contact.html (generated ...

What is the method for launching Chrome synchronously in Selenium WebDriver using createSession()?

After executing the code below using Selenium WebDriver to launch a Chrome browser: import { Driver } from 'selenium-webdriver/chrome'; Driver.createSession(); console.log("I've launched!"); I'm encountering an issue where "I've ...

Standard layout for a project with equally important server and client components

We are in the process of developing an open-source library that will consist of a server-side component written in C# for Web API, meta-data extraction, DB operations, etc., and a client-side component written in TypeScript for UI development. Typically, ...

The Angular Observable continues to show an array instead of a single string value

The project I am working on is a bit disorganized, so I will try to explain it as simply as possible. For context, the technologies being used include Angular, Spring, and Maven. However, I believe the only relevant part is Angular. My goal is to make a c ...

The function for utilizing useState with a callback is throwing an error stating "Type does not have

Currently, I am implementing the use of useState with a callback function: interface Props { label: string; key: string; } const [state, setState] = useState<Props[]>([]); setState((prev: Props[]) => [...pr ...

Unable to send JSON data from server to client following a successful file upload operation

I'm currently working on a project that involves NodeJS, Express, JQuery, and Typescript. The issue I'm facing is related to uploading a file from the front end, which is successful. However, I'm encountering difficulties in returning a JSON ...

Building a Vuetify Form using a custom template design

My goal is to create a form using data from a JSON object. The JSON data is stored in a settings[] object retrieved through an axios request: [ { "id" : 2, "name" : "CAR_NETWORK", "value" : 1.00 }, { "id" : 3, "name" : "SALES_FCT_SKU_MAX", "val ...

Using the TranslateService in Angular to externalize an array of strings

I am new to externalizing code. As I was working on developing a month picker in Angular, I initially had an array of months with hardcoded names in my typescript file: arr = ['Jan', 'Feb', 'Mar', 'Apr', 'May&a ...

Combining files/namespaces/modules in Typescript: How to do it?

Even though I believe the solution may be simple, understanding how to merge enums across multiple files is eluding me when reading through the documentation. // a.ts enum Color{ RED, BLUE } // b.ts enum Day{ MONDAY, TUESDAY } // c ...

Encountering a Problem with HTTP Requests in Angular 2

Seeking assistance with a technical issue. My objective: Make a REST API call to retrieve JSON data and resolve an Angular 2 promise. ServerAPI built with Node.js/ExpressJS/Lodash Sample of server.js file: var express = require('express'); va ...

What is the best approach for implementing recursion within a foreach loop in TypeScript?

Problem Situation I am attempting to develop a customized typewriting effect similar to the one demonstrated here with a 100ms delay using Angular. The TypeScript code I have written for this purpose is as follows: private arr: string[] = ["Lead Dev ...

Encountering a problem with lazy loading of module routing paths. Issue arises when attempting to navigate to http://localhost:4200

AppLazyLoadingMoudle import {NgModule} from '@angular/core'; import {RouterModule, Routes} from '@angular/router'; const ROUTES : Routes = [ /* ProductModule (defined in the feature module) is loaded lazily when navigating ...

Leverage a nearby module with a local dependency

My current challenge involves integrating a local library into my project. I have been following two tutorials: how to create a library and how to consume a local library. Despite having a well-structured sample library with package.json and index.ts, I am ...

Best practice for encapsulating property expressions in Angular templates

Repeating expression In my Angular 6 component template, I have the a && (b || c) expression repeated 3 times. I am looking for a way to abstract it instead of duplicating the code. parent.component.html <component [prop1]="1" [prop2]="a ...

The system does not acknowledge 'NODE_OPTIONS' as a command that can be used internally or externally, or as an operational program or batch file

While trying to build my react + vite project, I encountered an error after running npm run build. https://i.stack.imgur.com/XfeBe.png Here is a snapshot of my package.json file. https://i.stack.imgur.com/MbbmY.png ...

Setting property values in Typescript by initializing them from other properties when reading objects from a subscription response

I have created a basic class structure export class SampleObj{ item1: string; item2: string; item3: string; } I am fetching data from the backend and populating this class using HttpClient: this.httpClient.get<SampleObj[]>(`backendUrl`).subscr ...

Combining rxjs events with Nestjs Saga CQRS

I am currently utilizing cqrs with nestjs Within my setup, I have a saga that essentially consists of rxjs implementations @Saga() updateEvent = (events$: Observable<any>): Observable<ICommand> => { return events$.pipe( ofType(Upd ...

How can I utilize Angular and TypeScript to loop through the innerHTML property binding effectively?

I'm currently working on using ngFor to display the binding details of the innerHtml property. <ul> <li *ngFor="let n of NotificationData"> {{n.NotificationID}} <label [innerHtml]="n.NotificationText | StyleHtml&quo ...