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

The functionality of the KendoReact Grid for filtering and sorting is not functioning correctly when data is grouped

Once I group the data, the filter and sort functions in the KendoReact Grid stop working. Interestingly, if I bypass the grouping step and show the data without grouping, the filter and sort functions function perfectly fine. My main objective is to figu ...

Fetching JSON data using Promise.all results in an empty response

I'm facing an issue in my code where I am trying to fetch data from two different JSON files and then return them as arrays. Even after implementing the solution below, it doesn't seem to be working as expected. Can someone guide me on how I can ...

The MaterialTable component is indicating that there is no property called 'tableData' on the IPerson type

Incorporated an editable attribute to my MaterialTable component. Currently looking for a way to retrieve the index of updated or deleted items within the onRowUpdate and onRowDelete methods. To replicate the issue, refer to this minimal sandbox example: ...

Is it possible to capture and generate an AxiosPromise inside a function?

I am looking to make a change in a function that currently returns an AxiosPromise. Here is the existing code: example(){ return api.get(url); } The api.get call returns an object of type AxiosPromise<any>. I would like to modify this function so ...

The typescript error "Cannot read properties of undefined" is encountered while trying to access the 'map' function

I was attempting to follow a guide on creating an app using typescript and react, but I'm encountering an error that says "Cannot read properties of undefined (reading 'map')". I'm not sure why this is happening, can someone please offe ...

Problem integrating 'fs' with Angular and Electron

Currently, I am working with Angular 6.0, Electron 2.0, TypeScript 2.9, and Node.js 9.11 to develop a desktop application using the Electron framework. My main challenge lies in accessing the Node.js native API from my TypeScript code. Despite setting "com ...

The Angular library files built with ng build are not automatically included in the dist folder

My Angular 9 library has a project structure similar to the one shown below After running ng build falcon-core to build the library, I noticed that the view-model files are missing from the dist folder I couldn't find any settings in the tsconfig.li ...

Sending VSCode to external functions

My primary entrypoint containing the activate() function is: extension.ts import * as vscode from "vscode"; import { subscribe } from "./eventListeners.ts"; export function activate(context: vscode.ExtensionContext) { vscode.command ...

One way to incorporate type annotations into your onChange and onClick functions in TypeScript when working with React is by specifying the expected

Recently, I created a component type Properties = { label: string, autoFocus: boolean, onClick: (e: React.ClickEvent<HTMLInputElement>) => void, onChange: (e: React.ChangeEvent<HTMLInputElement>) => void } const InputField = ({ h ...

The attribute 'disabled' is originally defined as a characteristic within the class 'CanColor & CanDisableRipple & HasTabIndex & MatChipBase'. However, it is replaced in the current context of 'MatChip' as an attribute

After updating my Angular version from 9.1 to 11, I encountered a compilation error. Error: node_modules/@angular/material/chips/chips.d.ts:120:9 - error TS2611:'disabled' is defined as a property in class 'CanColor & CanDisableRipple &a ...

A method for modifying the key within a nested array object and then outputting the updated array object

Suppose I have an array called arr1 and an object named arr2 containing a nested array called config. If the key in the object from arr1 matches with an id within the nested config and further within the questions array, then replace that key (in the arr1 ...

Dynamically pass a template to a child component

How can I dynamically load content on my page based on the active navigation point? export class Sub_navigation_item { constructor( public title: string, public templateName: string ) {} } I have a navigation item with an ID from an ...

Image not found in next.js

Working Environment ・ next.js ・ typescript ・ styled-components I uploaded the image in the folder inside pages, but it is not showing up. Why is that? // package.json   { "name": "nextapp", "version": &qu ...

Struggling to combine interface with import and local variables - any solutions?

Although examples have demonstrated the merging of interfaces in a single file, I am facing challenges when trying to merge interfaces that are located in different files. I want to clarify that I am not extending any modules, just interfaces. /types/ind ...

What is the reason behind Rxjs switchMap only emitting the final value from an of() observable source?

Here are two code snippets, one using map and the other using switchMap. The functionality of map is clear: of('foo', 'bar') .pipe(map((val) => sanitizer(val))) .subscribe((val) => console.log('value:', val)); func ...

Error in unit testing: Trying to access property 'pipe' of an undefined variable results in a TypeError

Recently, I've been faced with the challenge of writing a unit test for one of the functions in my component. The specific function in question is called 'setup', which internally calls another function named 'additionalSetup'. In ...

What is the best way to set up TypeScript to utilize multiple node_modules directories in conjunction with the Webpack DLL plugin?

Utilizing Webpack's DllPlugin and DllReferencePlugin, I create a distinct "vendor" bundle that houses all of my main dependencies which remain relatively static. The project directory is structured as follows: project App (code and components) ...

Monitor modifications to documents and their respective sub-collections in Firebase Cloud Functions

Is it possible to run a function when there is a change in either a document within the parent collection or a document within one of its subcollections? I have tried using the code provided in the Firebase documentation, but it only triggers when a docume ...

Ways to transfer information among Angular's services and components?

Exploring the Real-Time Binding of Data Between Services and Components. Consider the scenario where isAuthenticated is a public variable within an Authentication service affecting a component's view. How can one subscribe to the changes in the isAut ...

Broaden material-ui component functionality with forwardRef and typescript

To enhance a material-ui component with typescript, I have the javascript code provided in this link. import Button from "@material-ui/core/Button"; const RegularButton = React.forwardRef((props, ref) => { return ( <B ...