Identifying the scenario where Partial<T> inherits from T

I am facing a scenario where I am working towards achieving a specific "state":

type State = { foo: number, bar: number, baz?: string };

Initially, I may not have reached the complete State yet but rather align with the structure of Partial<State>. My objective is to determine when I have successfully attained the desired State.

To assist me in this process, I developed a utility type named Resolve:

type Resolve<T extends Partial<State>> = 
    T extends infer U 
        ? U extends State ? State : Partial<State> 
        : never;

This utility functions well when handling inferred types like:

const implicit1 = { foo: 5 };
const implicit2 = { foo: 5, bar: 10 };
// Resolves correctly to Partial<State>
const implicitResolve1: Resolve<typeof implicit1> = implicit1;
// Resolves correctly to State
const implicitResolve2: Resolve<typeof implicit2> = implicit2;

However, once a type has been defined as a Partial<State>, it fails to recognize that it could be of type State:

const explicit1: Partial<State> = { foo: 5 };
const explicit2: Partial<State> = { foo: 5, bar: 10 };
// Resolves correctly to Partial<State>
const explicitResolve1: Resolve<typeof explicit1> = explicit1;
// Unfortunately resolves to Partial<State> despite the intended State type
const explicitResolve2: Resolve<typeof explicit2> = explicit2;

In my overall solution, I already had a type guard ready and I believed it would provide me with the necessary capabilities:

type TypeGuard<T> = (thing: unknown) => thing is T;
const tg: TypeGuard<State> = (thing: unknown): thing is State => {
    return typeof thing === "object" 
      && typeof (thing as any)?.foo === "number" 
      && typeof (thing as any)?.bar === "number";
};
function SuperResolve(thing: unknown, tg: TypeGuard<State>) {
    return tg(thing) ? thing as State : thing as Partial<State>
}
// Surprisingly, this also resolves to Partial<State>!
const fnResolve = SuperResolve(explicit2, tg);

At this point, I feel like I've exhausted all my options... there must be a way to identify when a Partial<T> has transitioned into <T>.

Code Playground

Answer №1

When you specify a variable to have a single object type like Partial<State> (similar to

{ foo?: number, bar?: number, baz?: string }
), the TypeScript compiler will not narrow down the apparent type of the variable if you assign a value that is more specific.

This narrowing based on control flow only occurs with variables or properties that are union types, and Partial<State> does not fall under this category.

So, as soon as you declare

const x: Partial<State> = { foo: 5, bar: 10 };

any details about the specific values assigned to x are lost to the compiler. Regardless of what you assign, x remains of type Partial<State>. You can utilize a user-defined type guard for conditional narrowing, but it doesn't affect the actual value assigned to x:

if (tg(x)) {
    x.bar.toFixed(2); // works fine
} 

If you want the compiler to recognize that x can be assigned to State, avoid prematurely widening it to Partial<State>. Instead, let the compiler deduce the type of x itself:

const x = { foo: 5, bar: 10 }; // no issues

Anything expecting either a State or a Partial<State> will accept x due to TypeScript's structural type system:

function takeState(state: State) { }
takeState(x); // works fine

If you want to ensure that x truly remains a Partial<State> after assignment and not just catch an error later, you could use a utility function like

const asPartialState = <T extends Partial<State>>(t: T) => t;

And confirm that it aligns with Partial<State> without introducing any widerning:

const y = asPartialState({ foo: 5, bar: 10 }); // all good
takeState(y); // all good

const z = asPartialState({ foo: 5, bar: "oops", baz: "" }); // error!
// ------------------------------> ~~~
// Type 'string' is not assignable to type 'number | undefined'.

None of these methods enable you to "build" a Partial<State> into a State, as the type of a variable remains unchanged regardless of assignments. Therefore, things like:

const x = { foo: 5 }
x.bar = 10; // error!
takeState(x); // error!

const y: Partial<State> = { foo: 5 }
y.bar = 10; // okay
takeState(y); // error!

In scenarios where manual assignment of each property is used for building, an assertion function may be utilized for controlling the flow:

function setProp<T extends object, K extends PropertyKey, V>(
    obj: T, key: K, val: V
): asserts obj is T & Record<K, V> { (obj as any)[key] = val }

const x = { foo: 5 }
setProp(x, "bar", 10); // no errors now
takeState(x); // no errors

If a more intricate or abstract building process is desired where properties are looped over or passed to other non-assertion functions, even this approach won't work smoothly:

const x = {};
(["foo", "bar"] as const).forEach(k => setProp(x, k, 5));
takeState(x); // error!

In such cases, relying on the compiler to track complex control flows from Partial<State> to State might not be feasible. In those instances, asserting the type conversion or using a runtime check through a type guard function can be considered:

takeState(x as State); // no error, but potential risks

or performing an unnecessary runtime validation using your type guard function:

if (!tg(x)) throw new Error("My life is a lie"); 
takeState(x); // all good

Playground link to code

Answer №2

State represents a full subset of Partial<State>, which is why

State | Partial<State> == Partial<State>
.

The only way to convert Partial<State> into State is by explicitly setting Required<State> or by using type-fest and setting

SetRequired<Partial<State>, "foo" | "bar">
(but this implies that the keys can be extracted from somewhere).

In the following code:

const explicit2: Partial<State> = { foo: 5, bar: 10 };
const explicitResolve2: Resolve<typeof explicit2> = explicit2;

there is an error because when you set : Partial<State>, you have removed all information regarding obligations, regardless of what comes after the =.

I recommend using two Type Guards to confirm the necessary types. One to check if the argument is a Partial<State> and another to determine if it is a State. Run these checks in separate if statements.

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

Multiple asynchronous calls in Angular 2

In my Component, there is a function that is supposed to return a value (Promise). This value requires information from two distinct sources: an API call and data from a database. The method in question looks like this: public getValue(): Promise<numb ...

Ensuring a User has an Image in MySQL Using Angular 6

As part of my development process, I am working on creating a new user and sending their information along with an image to a MySQL database. The process involves sending a user object with form data through the following component.ts file: subscribeUser() ...

Easy steps to bring in type definitions from an npm package using Vite

I am currently developing a package with several ts functions that will be utilized by multiple repositories, including mobile and web applications. In our team, we use vite as our primary build tool, which is also integrated into the repository. Below is ...

What is the reason behind VS Code not showing an error when executing the command line tsc shows an error message?

Deliberately introducing a typo in my code results in an error. Here is the corrected code: declare const State: TwineState; If I remove the last character and then run tsc on the command line, it throws this error: tsc/prod.spec.ts:7:22 - error TS2304: ...

How can I display an agm-polyline within a map in Angular 7?

I need assistance with adjusting the polylines on my map and dynamically setting the zoom level based on their size and position. Here is the code I am currently using: <agm-map style="height: 500px" [latitude]='selectedLatitude' [longitude ...

Conceal a row in a table using knockout's style binding functionality

Is it possible to bind the display style of a table row using knockout.js with a viewmodel property? I need to utilize this binding in order to toggle the visibility of the table row based on other properties within my viewmodel. Here is an example of HTM ...

Conditional Rendering with Next.js for Smaller Displays

I've implemented a custom hook to dynamically render different elements on the webpage depending on the screen size. However, I've noticed that there is a slight delay during rendering due to the useEffect hook. The conditionally rendered element ...

When using TypeScript, the tls.TLSSocket() function may trigger an error mentioning the absence of a "socket" parameter

Currently, I am in the process of building a basic IRC bot and using raw sockets to connect to the IRC server. Initially written in plain Javascript, I am now transitioning it to TypeScript. However, I have encountered an unusual issue when attempting to c ...

"Encountered a problem when trying to import stellar-sdk into an Angular

Our team is currently working on developing an app that will interact with the Horizon Stellar Server. As newcomers in this area, we are exploring the use of Angular 8 and Ionic 4 frameworks. However, we have encountered difficulties when trying to import ...

What is the best way to utilize "exports" in package.json for TypeScript and nested submodules?

Looking to leverage the relatively new "exports" functionality in Node.js/package.json for the following setup: "exports": { ".": "./dist/index.js", "./foo": "./dist/path/to/foo.js" } so that ...

Is it advisable to use an if statement or question mark in TypeScript to prevent the possibility of a null value?

Currently delving into TypeScript and exploring new concepts. I encountered a scenario where inputRef.current could potentially be null, so I opted to directly use a question mark which seems to work fine. However, in the tutorial video I watched, they use ...

What improvements can I make to enhance my method?

I have a block of code that I'm looking to clean up and streamline for better efficiency. My main goal is to remove the multiple return statements within the method. Any suggestions on how I might refactor this code? Are there any design patterns th ...

Having trouble with obtaining real-time text translation using ngx translate/core in Angular 2 with Typescript

Issue : I am facing a challenge with fetching dynamic text from a JSON file and translating it using the translate.get() method in Angular2. this.translate.get('keyInJson').subscribe(res => { this.valueFromJson = res; /* cre ...

Having trouble getting ng-click to function properly in TypeScript

I've been struggling to execute a function within a click function on my HTML page. I have added all the TypeScript definition files from NuGet, but something seems to be going wrong as my Click Function is not functioning properly. Strangely, there a ...

State array is being updated

In my main container, I am setting a context for its children : import React, {useRef, useEffect, useState, ReactNode, createContext, useContext} from 'react'; import Provider from "./Provider"; import Consumer from "./Consumer&quo ...

regex execution and testing exhibiting inconsistent behavior

The regex I am using has some named groups and it seems to match perfectly fine when tested in isolation, but for some reason, it does not work as expected within my running application environment. Below is the regex code that works everywhere except in ...

Error: Unable to generate MD5 hash for the file located at 'C:....gradle-bintray-plugin-1.7.3.jar' in Ionic framework

When attempting to use the command ionic cordova run android, an error occurred that prevented the successful execution: The process failed due to inability to create an MD5 hash for a specific file in the specified directory. This issue arose despite suc ...

Encountered an error while attempting to load module script

Upon launching an Angular application on Heroku, a situation arises where accessing the URL displays a blank page and the console reveals MIME type errors. The error message reads: "Failed to load module script: The server responded with a non-JavaScrip ...

What could be causing the primeng dialog to appear blank when conducting Jasmine tests on this Angular TypeScript application?

Having trouble testing a component due to rendering issues? Check out the code snippet below: import {ChangeDetectionStrategy, Component, EventEmitter, Input, Output} from '@angular/core'; @Component({ selector: 'app-help', cha ...

Before running any unit tests, I have to address all linting issues as required by ng test

Upon running ng test, the output I receive is as follows: > ng test 24 12 2019 14:20:07.854:WARN [karma]: No captured browser, open http://localhost:9876/ 24 12 2019 14:20:07.860:INFO [karma-server]: Karma v4.4.1 server started at http://0.0.0.0:9876/ ...