I'm trying to define a type that matches any object/dictionary but NOT arrays.
My first attempt didn't work since arrays are technically objects under the hood:
const a:{[k:string]: any} = []; // works fine
I also know that it's possible to create a generic "checker" like so:
type NoArray<T> = T extends any[] ? never : T;
But that's not what I'm looking for. I want a non-generic type that works like this:
const a: NoArrayType = {}; // works fine
const a: NoArrayType = []; // TypeError
In TypeScript, object is the type of all non-primitive values (primitive values are undefined , null , booleans, numbers, bigints, strings). With this type, we can't access any properties of a value.
In Typescript, Type assertion is a technique that informs the compiler about the type of a variable. Type assertion is similar to typecasting but it doesn't reconstruct code. You can use type assertion to specify a value's type and tell the compiler not to deduce it.
What is a type in TypeScript. In TypeScript, a type is a convenient way to refer to the different properties and functions that a value has. A value is anything that you can assign to a variable e.g., a number, a string, an array, an object, and a function. See the following value: 'Hello'
In Typescript, Type aliases give a type a new name. They are similar to interfaces in that they can be used to name primitives and any other kinds that you'd have to define by hand otherwise. Aliasing doesn't truly create a new type; instead, it gives that type a new name.
In JavaScript, the fundamental way that we group and pass around data is through objects. In TypeScript, we represent those through object types. As we’ve seen, they can be anonymous: or a type alias.
In TypeScript, we represent those through object types. As we’ve seen, they can be anonymous: or a type alias. In all three examples above, we’ve written functions that take objects that contain the property name (which must be a string) and age (which must be a number ).
One of which is Array of Objects, in TypeScript, the user can define an array of objects by placing brackets after the interface. It can be named interface or an inline interface.
Argument of type 'number' is not assignable to parameter of type 'string'. Argument of type 'number' is not assignable to parameter of type 'string'. Even if you don’t have type annotations on your parameters, TypeScript will still check that you passed the right number of arguments. You can also add return type annotations.
Type problem is the any
in your type declaration. any
is usually something you want to avoid in most typescript applications.
An array is just an object that can be indexed with numeric keys and has some extra methods. In fact you can assign pretty much any non primitive value to that type.
const a: {[k:string]: any} = [1,2,3]; // works
const b: {[k:string]: any} = {a: 123}; // works
const c: {[k:string]: any} = () => { console.log(123) }; // works
const d: {[k:string]: any} = () => new AnyClass(); // works
Playground
This works for the same reason you can do the following, because any
is the one case where typescript always lets you cast a value to.
const a: any = true
const b: any = {}
const c: any = new AnyClass()
Playground
So you have a few options.
any
. If you know what possible values are on those properties, declare them.interface MyObjectType { [k: string]: number | string }
const a: MyObjectType = [] // fails
const b: MyObjectType = {} // works
Playground
Or perhaps this is JSON
? If so, any
isn't the right type since you know it can't have some things (like class instances or functions).
interface Json {
[key: string]: string | number | boolean | Json | Json[]
}
const a: Json = [] // type error
const b: Json = {} // works
Playground
unknown
type instead of any
, which requires that you check the type at runtime before using the values.interface MyObjectType { [k: string]: unknown }
const a: MyObjectType = [] // type error
const b: MyObjectType = { prop: 123 } // works
// b.prop type here is: unknown
b.prop.toUpperCase() // type error
if (typeof b.prop === 'string') {
// a.prop type here is: string
console.log(b.prop.toUpperCase()) // works
}
Playground
Here's what I came up with, using a type intersection instead of an index signature:
/**
* Constrains a type to something other than an array.
*/
export type NotArray = (object | string | bigint | number | boolean) & { length?: never; };
This allows more than what the original poster was looking for, but it can easily be adjusted:
/**
* Constrains a type to an object other than an array.
*/
export type NonArrayObject = object & { length?: never; };
The advantage of not using an index signature is that you get an error if you access a property which doesn't exist:
function test(hello: NonArrayObject) {
return hello.world; // error
}
The disadvantages of using … & { length?: never; }
are that you can access the length
property of NotArray
, that you cannot use it for objects which happen to have a length
property, and that you cannot use it for functions because they have a length
property.
And if anyone is wondering, I use NotArray
to define optional return values, where in most cases only the first return value is of interest:
export type OptionalReturnValues2<T1 extends NotArray, T2> = T1 | [T1 | undefined, T2];
export function normalizeReturnValues2<T1 extends NotArray, T2>(optionalReturnValues: OptionalReturnValues2<T1, T2>): [T1 | undefined, T2 | undefined] {
if (Array.isArray(optionalReturnValues)) {
return optionalReturnValues;
} else {
return [optionalReturnValues, undefined];
}
}
export type OptionalReturnValues3<T1 extends NotArray, T2, T3> = T1 | [T1 | undefined, T2] | [T1 | undefined, T2 | undefined, T3];
export function normalizeReturnValues3<T1 extends NotArray, T2, T3>(optionalReturnValues: OptionalReturnValues3<T1, T2, T3>): [T1 | undefined, T2 | undefined, T3 | undefined] {
if (Array.isArray(optionalReturnValues)) {
return [optionalReturnValues[0], optionalReturnValues[1], optionalReturnValues[2]];
} else {
return [optionalReturnValues, undefined, undefined];
}
}
This seems to do what you need:
type NonArrayObject = {
[x: string]: any
[y: number]: never
}
let p: NonArrayObject = {} // fine
let q: NonArrayObject = { foo: "bar" } // fine
let r: NonArrayObject = [] // type error
let s: NonArrayObject = ["foo", 3] // type error
Edit: The type error for empty arrays seems to be a local artefact. In the playground it only seems to prevent populated arrays. Maybe that helps a little :)
This is what I came up with, which works and has a somewhat helpful error message where others have a mostly unhelpful error message:
type NotArray = {
length?: never
[key: string]: any
} | string | bigint | number | boolean
const h: NotArray = [1] // errors
const h1: NotArray = ['1'] // errors
const h3: NotArray = [{h: 'h'}] // errors
const h4: NotArray = {h: ['h']}
const h5: NotArray = {h: 1}
const h2: NotArray = 1
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With