Combining Types in TypeScript: Overriding Field Types

Can someone help me understand the behavior of this sample code?

type A = {
  field: string
}

type B = {
  field: number
}

//I expect A | B is equivalent to field: A["field"] | B["field"]

type AORB = A | B

type AORBField = {
  field: A["field"] | B["field"]
}

type BaseTypes = {
  field: number | string
}

//Toggle between AORB and AORBField
type AB = AORB;
//type AB = AORBField;
//type AB = BaseTypes

const isA = (t: AB): t is A => {
  return typeof t.field === "string";
}

const isB = (t: AB): t is B => {
  return typeof t.field === "number";
}

const test = (x: AB) => {}
const testA = (x: A) => {}
const testB = (x: B) => {}
const testString = (x: string) => {}
const testNumber = (x: number) => {}

const getField = () => {
  const val = Math.random();
  return Math.random() % 2 ? val.toString(): val
}

const getDummyObject = () => {
  const val = Math.random();
  return { field: Math.random() % 2 ? val.toString(): val }
}

//Why error?
const ab: AB = {
  //field: 1 //This will pass for AORB
  //field: "string" //This will pass for AORB
  field: getField() //This will pass for AORBFields
}

//Why error?
const abc: AB = getDummyObject();


if (isA(ab)){
  const a: A = ab;
  testString(a.field)
  testNumber(a.field) //Expected err
  testA(a)
  test(ab)
}
else
{
  const b: B = ab; //This will fail for AORBField and BaseTypes, but that is expected since else statement cannot figure out main type
  testString(b.field) //Expected err
  testNumber(b.field)
  testB(b)
  test(ab)
}

I'm puzzled by the TypeScript errors on ab and abc assignments. I thought AORB = AORBField = BaseTypes would allow similar assignments. Can anyone shed some light on this issue?

Answer №1

When it comes to combining object types in a union, things don't quite work the way you might expect. The return type of your getDummyObject function is { field: string | number }, which cannot be assigned to either type A or B. Therefore, attempting to assign it to the union type A | B also doesn't work because it doesn't satisfy either type within the union. Essentially, these two types just don't blend together as seamlessly as you may have hoped.

type A = {
  field: number
}

type B = {
  field: string
}

type AB = A | B

// return type of getDummyObject
type Neither = {
  field: string | number
}

type Test1 = Neither extends A ? A : never   // never
type Test2 = Neither extends B ? B : never   // never
type Test3 = Neither extends AB ? AB : never // never

This logic makes sense when you consider that an object cannot simultaneously be both an A and a B. It's hard to assign something that falls outside of both categories to a variable that can only hold one or the other.

If you modify the code inside the getDummyObject function to:

const getDummyObject = () => {
  const val = Math.random();
  return Math.random() % 2 ? 
    { field: val.toString() } : 
    { field: val };
}

then everything works smoothly:

const foo: AB = getDummyObject(); // no error

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

Is it necessary to declare parameters for the super class constructor in Typescript React? If so, what would those parameters be?

I'm struggling to understand the specifics of the constructor in this code. Can anyone clarify what arguments the constructor for super() and constructor() should have? import * as React from "react"; import styles from "./ScriptEditor.module.scss"; ...

Tips for invoking a function from a different component in Angular?

I'm currently working on implementing a method call from one Angular component to another Inside one.component.ts, there is a task to verify if it's a day off export class CalendarComponent implements OnInit { isDayOfWeekDisabled(dayOf ...

Is there a way to determine the tally of users who have achieved a score greater than that of [userID]?

I have this SQL code that helps to determine the position of a specific entry based on its score compared to other entries. select (select count(*) from entries e2 where e1.score < e2.score) + 1 as pos from entries e1 where e1.ID = 36; Transla ...

What is the syntax for creating multi-line function definitions in TypeScript?

When it comes to writing functions with numerous parameters, do you typically format them like this: function foo( a: number, b: string, c: boolean): boolean { .... } or do you prefer formatting them like this: function foo( a: number, ...

The issue arises when TypeScript is unable to accurately infer the type as not being undefined following a type guard condition using

The following code snippet illustrates that in the else statement, it is evident that b cannot be undefined (a||b returns truthy and since a is falsy, b must be truthy). Why does Typescript show the error 'b' is possibly 'undefined', a ...

Using React with Typescript: Anticipating child component with particular props

I'm currently developing a component that necessitates the use of two specific child components. These two components are exported using dot notations from the main component and have defaultProps for identification within the main component: export ...

Error encountered: Dev server does not have an interface defined

I recently utilized the react-ts template to create a project with vite v5. However, when I run the application using pnpm dev, an error message pops up: App.tsx:9 Uncaught ReferenceError: CharacterConnectionStatus is not defined at App.tsx:9:22 Look ...

Creating Test Cases for Service Response Validation

I am currently attempting to unit test an API within my ngOnInit method. The method is responsible for making a call to the service in order to fetch details. If the details are not undefined, an array called 'shoeDataResponse' of type *shoeData ...

Verify whether a component is a React.ReactElement<any> instance within a child mapping operation

I am facing a challenge with a component that loops through children and wraps them in a div. I want to exclude certain elements from this process, but I am struggling to determine if the child is a ReactElement or not (React.ReactChild can be a string or ...

An error is triggered by the EyeDropper API stating that 'EyeDropper' has not been defined

I am trying to utilize EyeDropper for an eyedropper function in my project that uses Vue2 + Ts. Here is the code snippet: <div v-if="haveEyeDropper" @click="handleClickPick" > <i class="iconfont icon-xiguan"> ...

Tips for creating a TypeScript generic that ensures no SubType property is overly restricted

I am looking to create a function that can generically return a partial object of a specific type, where the object has an ID property. When working with a concrete type, such as the example below, the function works smoothly: type Person = { id: string; ...

How can I store an array of objects in a Couchbase database for a specific item without compromising the existing data?

Here is an example: { id:1, name:john, role:[ {name:boss, type:xyz}, {name:waiter, type:abc} ] } I am looking to append an array of objects to the existing "role" field without losing any other data. The new data should be added as individual ob ...

There was an error encountered while creating a new CLI using oclif: Subsequent variable declarations must be of the same type

I've been working on developing a new CLI tool using the oclif framework and TypeScript, but I'm encountering some issues when attempting to build the project. When I generate the CLI, it results in errors. Even when I try to manually run npm bu ...

Navigating to the tsconfig.json file based on the location of the file being linted

In my monorepo, each package currently contains a .eslintrc.cjs file with the following setup: Package-specific ESLint Configuration const path = require('path') const ts = require('typescript') const OFF = 0 const WARN = 1 const ERROR ...

I'm searching for TypeScript interfaces that can be used to define OpenAPI Json. Where can I

If you're looking to implement the OpenApi specifications for your project, there are a variety of fields and values that need to be set. For a detailed look at these specifications, you can refer to the documentation here. In an effort to streamline ...

The combination of Autodesk Forge Viewer and React with TypeScript provides a powerful platform for developing

I'm brand new to React and Typescript, and I have a very basic question. In the viewer documentation, extensions are defined as classes. Is it possible to transform that class into a typescript function? Does that even make sense? For example, take th ...

When using RXJS, the method BehaviorSubject.next() does not automatically notify subscribers

In my project, I have a service set up like this: @Injectable({ providedIn: 'root' }) export class MyService { private mySubject = new BehaviorSubject({}); public currentData = this.mySubject.asObservable(); updateData(data: any) { ...

How can we stop the parent modal from closing if the child component is not valid?

I have a main component with a modal component that takes another component as a parameter. The child modal component has some logic where I need to check if the child component is valid before closing the modal. const MainComponent: FC<IProps> => ...

Running a Playwright test without relying on the command line

Is it possible to automate running a playwright test without having to manually input npx playwright test in the command line every time? I am looking for a way to initiate a playwright file from another file and have it execute without the need for acce ...

Is there a way to use a single url in Angular for all routing purposes

My app's main page is accessed through this url: http://localhost:4200/ Every time the user clicks on a next button, a new screen is loaded with a different url pattern, examples of which are shown below: http://localhost:4200/screen/static/text/1/0 ...