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

"Can you show me how to create a dotted-object-path type from an Object

Is there a way to use ts-toolbelt's Object.Paths and String.Join to generate all paths of an object as String literals with dot separators, creating a Union type like the example below? // Object const obj = { a: 'abc', b: 'def&apos ...

Using the css function within styled-components

Struggling with implementing the media templates example from the documentation and figuring out how to type the arguments for the css function in plain JS: const sizes = { desktop: 992 } const media = Object.keys(sizes).reduce((acc, label) => { ...

Error Alert: Redux unable to retrieve data from Django API due to CORS issue

Currently, I am working on a project with a frontend application running on http://localhost:3000 and a backend API on http://localhost:8000. However, I am facing a CORS issue when trying to make requests from the frontend to the backend API. The error me ...

Underscore.js is failing to accurately filter out duplicates with _uniq

Currently, I am integrating underscorejs into my angular project to eliminate duplicate objects in an array. However, I have encountered an issue where only two string arrays are being kept at a time in biddingGroup. When someone else places a bid that is ...

When the key property is utilized, the state in react useState is automatically updated; however, for updating without it, the useEffect or a

I am working on a component that looks like this: import React, { useState } from "react"; import { FormControl, TextField } from "@material-ui/core"; interface IProps { text?: string; id: number; onValueChange: (text: stri ...

Enhancing React with TypeScript: Best Practices for Handling Context Default Values

As I dive into learning React, TypeScript, and Context / Hooks, I have decided to create a simple Todo app to practice. However, I'm finding the process of setting up the context to be quite tedious. For instance, every time I need to make a change t ...

Creating a personalized NPM package: Converting and exporting TypeScript definitions

Question: Do I need to adjust my TS configuration or add a TS build step? I recently developed a new npm package: Custom-NPM-Package / - src -- index.js -- index.d.ts -- IType.ts accompanied by this tsconfig.json: { "compilerOptions" ...

Key Assignment in Vue FireStore - Potential Undefined Object Situation

My goal is to assign Firestore data, passed through props, to a reactive proxy object in Vue. However, I am encountering an error that says: Object is possibly 'undefined'. (property) fireStoreData: Record<string, any> | undefined To strea ...

I am encountering an issue where body-parser is not functioning properly with typescript. Whenever I make a request, the request.body is returning as undefined

Below is the code snippet for my Express application using TypeScript version 3.7.4: import bodyParser from "body-parser"; import config from "config"; import cookieParser from "cookie-parser"; import express from "express"; import mongoose from "mongoose ...

The NullInjector has issued an error regarding the lack of a provider for the Decimal

I recently integrated lazy loading into my application. However, one of my services is in need of the DecimalPipe. The structure of my modules goes like this: service -> shared module -> App module To give you more context, I have already added "Co ...

Choose the object's property name using TypeScript through an interface

Consider a simplified code snippet like the following: interface MyBase { name: string; } interface MyInterface<T extends MyBase> { base: MyBase; age: number; property: "name" // should be: "string" but only p ...

Guide on accessing js file in an Angular application

I have a component where I need to create a function that can search for a specific string value in the provided JavaScript file. How can I achieve this? The file path is '../../../assets/beacons.js' (relative to my component) and it's named ...

Encountering an issue during the initialization of the Google Passportjs

I recently made the switch from JavaScript to TypeScript in my server project and I'm currently tidying up some code. I decided to combine my Google Passport OAuth stuff and login routes into a single file, but it seems like I've broken something ...

Tips for utilizing an object key containing a dash ("-") within it

Here is an example of the object structure: { approved_for_syndication: 1 caption: "" copyright: "" media-metadata: (3) [{…}, {…}, {…}] subtype: "photo" } How can I properly a ...

How can you display or list the props of a React component alongside its documentation on the same page using TypeDoc?

/** * Definition of properties for the Component */ export interface ComponentProps { /** * Name of something */ name: string, /** * Action that occurs when component is clicked */ onClick: () => void } /** * @category Componen ...

What is the process of 'initializing' an object in TypeScript?

Is it possible that retrieving a json from a mongodb database and casting it does not trigger the typescript constructor? What could be causing this issue? I have a Team class export class Team { transformations: { [transformationId: string]: Transfor ...

Guide to developing a personalized useReducer with integrated decision-making and event activation

I am interested in creating a custom hook called useTextProcessor(initialText, props). This hook is designed for managing and manipulating text (string) within a React state. It utilizes useReducer to maintain a cumulative state. Here is the implementation ...

Tips for iterating over an array that implements either of two interfaces in TypeScript

The objective is to develop a reusable method for filtering out items from an array that implements one of two interfaces Providing a code example would be most helpful: interface IDuration { start: number; end: number; } interface IRelativeDuration ...

exploring the ins and outs of creating computed properties in TypeScript

How can I store an object with a dynamically assigned property name in an array, but unsure of how to define the array properly? class Driver { public id: string; public name: string; constructor(id , name) { this.id = id; th ...

Exploring Angular 2: How to Retrieve the Value of a Radio Button

How can I retrieve the value of the radio button that is clicked in app.component.html from within app.component.ts? app.component.html <div class="container"> <div class="row"> <div class="col-sm-3 well" style="width: 20%"> ...