Mikey
Mikey

Reputation: 3017

Define an empty object type in TypeScript

I'm looking for ways to define an empty object type that can't hold any values.

type EmptyObject = {}

const MyObject: EmptyObject = {
  thisShouldNotWork: {},
};

Objects with the type are free to add any properties. How can I force MyObject to always be an empty object instead?

My actual use case is using the EmptyObject type inside an interface.

interface SchemaWithEmptyObject {
  emptyObj: EmptyObject; 
}

Upvotes: 30

Views: 24464

Answers (6)

simPod
simPod

Reputation: 13446

Type-fest has a battle-tested definition of an empty object

https://github.com/sindresorhus/type-fest/blob/b9723d4785f01f8d2487c09ee5871a1f615781aa/source/empty-object.d.ts

declare const emptyObjectSymbol: unique symbol;
export type EmptyObject = {[emptyObjectSymbol]?: never};

You can read more why other versions are problematic here: https://github.com/sindresorhus/type-fest/issues/395.

Upvotes: 3

Terukazu Inoue
Terukazu Inoue

Reputation: 21

How about something like this with a private symbol?

// EmptyType.ts
const EMPTY_SYMBOL = Symbol();
export type EmptyType = {[EMPTY_SYMBOL]?: never};

It works correctly against an intersection union type.

import {EmptyType} from "./EmptyType";
type Union = EmptyType | { id: string };
const a: Union = {};
const b: string = a.id; // <-- error

TypeScript Playground

Upvotes: 0

cprcrack
cprcrack

Reputation: 19100

There are several options for defining an empty object type, and it completely depends on the side-effects you want regarding your linter and typescript errors when accessing or setting properties. These are the options I tried:

// Option A:
let objA: Record<any, never> = {}; // Typescript-ESLint error: Unexpected any. Specify a different type.
objA = { prop: 'value' }; // Typescript error: Type 'string' is not assignable to type 'never'.
console.log(objA.prop);
console.log(objA.nonExistingProp); // No error!!!

// Option B:
let objB: Record<string, never> = {};
objB = { prop: 'value' }; // Typescript error: Type 'string' is not assignable to type 'never'.
console.log(objB.prop);
console.log(objB.nonExistingProp); // No error!!!

// Option C:
let objC: Record<never, never> = {};
objC = { prop: 'value' };
console.log(objC.prop); // Typescript error: Property 'prop' does not exist on type 'Record<never, never>'
console.log(objC.nonExistingProp); // Typescript error: Property 'nonExistingProp' does not exist on type 'Record<never, never>'.

TLDR: Record<string, never> raises errors when setting properties to the empty object. Record<never, never> raises errors when accessing properties of the empty object. I have yet to find a solution that raises errors in both cases.

Personally I went with option C for my current use-case, because I wanted an error to happen if I try to access a property of an empty object, but you may not want that!

Upvotes: 2

Goran_Ilic_Ilke
Goran_Ilic_Ilke

Reputation: 838

Acording to VSC linter type emtyObj = Record<string, never> .

Upvotes: 8

Mike Fogel
Mike Fogel

Reputation: 3197

type EmptyObject = Record<any, never>

This is equivalent to Maciej Sikora's answer but makes use of the Record utility type.

Upvotes: 25

Maciej Sikora
Maciej Sikora

Reputation: 20132

type EmptyObject = {
    [K in any] : never
}

const one: EmptyObject = {}; // yes ok
const two: EmptyObject = {a: 1}; // error

What we are saying here is that all eventual properties of our EmptyObject can be only never, and as never has no representing value, creating such property is not possible, therefor the object will remain empty, as this is the only way we can create it without compilation error.

Upvotes: 39

Related Questions