The type of Object.values() is not determined by a union or Records

When utilizing TypeScript, the Object.values() function encounters issues in deducing the accurate type from a union of Records:

type A = Record<string, number>;
type B = Record<string, boolean>;

function func(value: A | B) {
    const properties = Object.values(value); // any[]
}

TS Playground

I anticipate properties to be either number[] or boolean[], yet it is indeed any[].

Is there a method to properly determine the type from Object.values()?

Answer №1

When the Object.values(v) function is called, the compiler attempts to match the variable v against a type of {[k: string]: T} for a generic argument T that it automatically infers (as per this call signature).

If v has a type like {[k: string]: Z}, then the compiler will infer T as

Z</code, leading to successful compilation. However, if <code>v
is a union of records such as
{[k: string]: X} | {[k: string]: Y}
, the inference process differs. The algorithm does not directly synthesize X | Y as the inferred type
T</code. Instead, it selects one candidate - let's say, <code>X
- and raises an error if
Record<string, X> | Record<string, Y>
cannot be assigned to Record<string, X>.

This intentional avoidance of synthesizing union types aims to prevent situations where every call would succeed, causing potential issues with mismatched types.

In your scenario, however, you desire this union inference. An open feature request at microsoft/TypeScript#44312 proposes a method to facilitate this inference, although it is not yet incorporated into the language.

Hence, relying on the compiler to directly infer the desired union type is not feasible.


Instead, we can explicitly specify the union type when calling Object.values(), like so: Object.values<X | Y>(v). This means in your case, you could use

Object.values<number | boolean>(value)
and achieve the desired outcome.

However, hardcoding the type argument may not be ideal as you want it to dynamically adapt based on the type of value. To compute the type X | Y from a value such as

{[k: string]: X} | {[k: string]: Y}
, we can utilize the typeof type query operator paired with indexed access types.

For example:

Object.values<typeof value[string]>(value); // okay

To summarize using a general example:

function foo() {
    type A = Record<string, X>;
    type B = Record;
    function func(value: A | B) {
        return Object.values(value);        
    }
    // func(value: Record<string, X> | Record<string, Y>): (X | Y)[]
}

Within foo(), the func() function accepts a value of type A | B and returns a value typed as (X | Y)[], without requiring manual specification of X | Y. By employing typeof value[string], the compiler effectively calculates and utilizes the appropriate inferred union type.

Playground link to code

Answer №2

Here is an example of how you can create the interface for a custom object construct called ObjectConstructorAlt.

declare interface ObjectConstructorAlt {
    values<T>(o: T):
    T extends any ?
        T extends Record<string|number, infer V> ? V[] 
            : T extends (infer V)[] ? V[]
            : any
        : never       
    ;
}
type A = Record<string, number>;
type B = Record<string, boolean>;
declare const a:A;
declare const b:B;
declare const ab:A|B;

Object.values(a); // number[]
Object.values(b); // boolean[]
Object.values(ab); // any[]

// actual implementation
const ObjectAlt: ObjectConstructorAlt = {
    values<T>(x: T){ 
        return Object.values(x as object) as any; 
    }
}
ObjectAlt.values(a); // number[]
ObjectAlt.values(b); // boolean[]
ObjectAlt.values(ab); // number[] | boolean[]

Typescript

If by some miraculous chance you were able to convince the TypeScript team to replace the current ObjectConstructor

interface ObjectConstructor {
    values<T>(o: { [s: string]: T } | ArrayLike<T>): T[];
    values(o: {}): any[];
    ...
}

with something like ObjectConstructorAlt, then you wouldn't need to apply a patch on your own. However, this scenario is highly unlikely as it could lead to longer compile times and potentially break other functionalities.

update - Just for your information, I raised this issue with TypeScript as an official concern and unfortunately, the existing behavior of Object.values/entries has been deemed not faulty. There are historical reasons behind this decision, which you can read about here.

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

"Encountering a TypeScript error when using React Query's useInfiniteQuery

I am currently utilizing the pokeApi in combination with axios to retrieve data import axios from 'axios' export const fetchPokemonData = async ({ pageParam = "https://pokeapi.co/api/v2/pokemon?offset=0&limit=20" }) => { try ...

Tips for fixing the error message "unable to access property 'property-name' of null"

I need assistance with retrieving data from a firebase database and storing it in an array using typescript. Below is the code snippet I am working with: export class ViewUserPage { public list = []; public ref = firebase.database().ref(); public ...

Is there a mistake in the TypeScript guide for custom typography in MUI5?

Currently, I am in the process of setting up custom typography variants in MUI5 by referencing this helpful guide: https://mui.com/customization/typography/#adding-amp-disabling-variants. As I follow step 2 and input the type definitions: declare module &a ...

The string returned from the Controller is not recognized as a valid JSON object

When attempting to retrieve a string from a JSON response, I encounter an error: SyntaxError: Unexpected token c in JSON at position In the controller, a GUID is returned as a string from the database: [HttpPost("TransactionOrderId/{id}")] public asyn ...

Combining two sets of data into one powerful tool: ngx-charts for Angular 2

After successfully creating a component chart using ngx-charts in angular 2 and pulling data from data.ts, I am now looking to reuse the same component to display a second chart with a different data set (data2.ts). Is this even possible? Can someone guide ...

What is the reasoning behind defaultValue possessing the type of any in TextField Material UI?

According to the Material UI guidelines, the component TextField specifies that its defaultValue property accepts the type any. I decided to experiment with this a bit and found that in practice, defaultValue actually supports multiple types. You can see ...

How can I efficiently map an array based on multiple other arrays in JavaScript/TypeScript using ES6(7) without nested loops?

I am dealing with 2 arrays: const history = [ { type: 'change', old: 1, new: 2 }, { type: 'change', old: 3, new: 4 }, ]; const contents = [ { id: 1, info: 'infor1' }, { id: 2, info: 'infor2' }, { id: ...

Mapping properties between objects in Typescript: transferring data from one object to another

Here are two different types and an object: type TypeX = { x: number; y: number; z: number; }; type TypeY = { u: number; v: number; w: number; }; initialObject: { [key: string]: TypeX }; The goal is to transfer the properties from an object of ...

Error encountered while loading a plugin in Typescript and RequireJS compilation process

Currently, I am working on a Typescript application in Visual Studio 2015 where RequireJS is used for loading modules. I have successfully loaded various modules from .ts classes and external libraries by using their typing .d.ts files. However, I have en ...

Error: Uncaught TypeError - Unable to access 'reduce' property of undefined value

Currently, I am focusing on implementing yup validation. Specifically for FileList validation, encountering an issue where leaving the input empty triggers the following error message: enter image description here Below is the code snippet in question: (C ...

What could be causing this function to malfunction?

Apologies for any inaccuracies in technical terms used here. Despite being proficient in English, I learned programming in my native language. I am currently working on a project using the latest version of Angular along with Bootstrap. I'm unsure if ...

Various types of generics within an object

Is there a way to achieve different types for the nested K type within a type like MyType? Here's an example: type Config<K> = { value: K; onUpdate: (value: K) => void; } type MyType<F extends string> = { [K in F]: <V>() =& ...

In the realm of JavaScript and TypeScript, the task at hand is to locate '*' , '**' and '`' within a string and substitute them with <strong></strong> and <code></code>

As part of our string processing task, we are looking to apply formatting to text enclosed within '*' and '**' with <strong></strong>, and text surrounded by backticks with <code> </code>. I've implemented a ...

The element is implicitly assigned an 'any' type as the expression of type 'any' cannot be used to index a type with createStyles

My stylesheet looks like this: const badgeStyle = createStyles({ badge: { borderRadius: "12px", padding: "5px 12px", textTransform: "uppercase", fontSize: "10px", fontWeight: 700, lineHeight ...

Having trouble importing the d3-geo package into a Node.js TypeScript project

Seeking a way to test the inclusion of specific latitude and longitude coordinates within different GeoJSON Features using code. When attempting this with: import d3 from 'd3-geo'; // or: import * as d3 from 'd3-geo' // no difference ...

Angular data binding between an input element and a span element

What is the best way to connect input texts with the innerHTML of a span in Angular6? Typescript file ... finance_fullname: string; ... Template file <input type="text" id="finance_fullname" [(ngModel)]="finance_fullname"> <span class="fullnam ...

What is the way to send custom properties to TypeScript in combination with StyledComponents?

Encountering an error while attempting to implement Styled Components 3 with TypeScript: TS2365: Operator '<' cannot be applied to types 'ThemedStyledFunction<{}, any, DetailedHTMLProps<TableHTMLAttributes<HTMLTableElement>, ...

Notify programmers about the potential risks associated with utilizing certain third-party components

Incorporating a 3rd party library into our codebase involves utilizing its components directly, although some are enclosed within internally created components. Is there a method available to alert developers when they try to use one of the wrapped compone ...

Conceal the Angular Material toolbar (top navigation bar) automatically when scrolling downwards

In my Angular application, the main navigation consists of a standard toolbar positioned at the top of the page. My goal is to have this navigation bar smoothly scroll up with the user as they scroll down, and then reappear when they scroll back up. I at ...

Can TypeScript be used to generate a union type that includes all the literal values from an input string array?

Is it feasible to create a function in TypeScript that takes an array of strings and returns a string union? Consider the following example function: function myfn(strs: string[]) { return strs[0]; } If I use this function like: myfn(['a', &a ...