300

Say I have:

type User = {
  ...
}

I want to create a new user but set it to be an empty object:

const user: User = {}; // This fails saying property XX is missing
const user: User = {} as any; // This works but I don't want to use any

How do I do this? I don't want the variable to be null.

2
  • 24
    Either you want user to be of type User | {} or Partial<User>, or you need to redefine the User type to allow an empty object. Right now, the compiler is correctly telling you that user is not a User. Commented Jul 26, 2017 at 23:43
  • 1
    Record<string, never>: typescript-eslint.io/rules/no-empty-object-type Commented Jun 29 at 15:42

12 Answers 12

513

Caveats

Here are two worthy caveats from the comments.

Either you want user to be of type User | {} or Partial<User>, or you need to redefine the User type to allow an empty object. Right now, the compiler is correctly telling you that user is not a User. – jcalz

I don't think this should be considered a proper answer because it creates an inconsistent instance of the type, undermining the whole purpose of TypeScript. In this example, the property Username is left undefined, while the type annotation is saying it can't be undefined. – Ian Liu Rodrigues

Answer

One of the design goals of TypeScript is to "strike a balance between correctness and productivity." If it will be productive for you to do this, use Type Assertions to create empty objects for typed variables.

type User = {
    Username: string;
    Email: string;
}

const user01 = {} as User;
const user02 = <User>{};

user01.Email = "[email protected]";

Here is a working example for you.

Here are type assertions working with suggestion.

Sign up to request clarification or add additional context in comments.

6 Comments

Thank you! This solves it! This should be marked as the correct answer...
I don't think this should be considered a proper answer because it creates an inconsistent instance of the type, undermining the whole purpose of TypeScript. In this example, the property Username is left undefined, while the type annotation is saying it can't be undefined.
Good point @IanLiuRodrigues. I added some caveats to the answer.
@IanLiuRodrigues One of the goals of TypeScript is to balance correctness with productivity, so to say that this undermines the whole purpose of TypeScript is a bit extreme. github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
Maybe my typescript is just really bad but I find the language counter-productive. I just want to do something like this (c#) Define a person: public class Person { int age; } then later initialise a person that is either null or just with empty props: var p = new Person(); // with empty props or Person p = null; // starts off null Why should it be so complex?
|
71

An empty object can be written as Record<string,never>, so effectively your type for user is either an empty object or a User

const user : User | Record<string, never> = {};

3 Comments

When I try to assign a value using data[key] = 'test I get the error Type 'any' is not assignable to type 'never'.ts(2322) any idea?
you can only assing an empty object or a user. Thats what we are declaring. If you want to assign string do an or like so const user : User | Record<string, never> = {}|string;
The problem with using Record<string, never> is types that use it are no longer extensible. For instance, if you wanted to add type UserExt = User & { phoneNumber: string }, trying to create an instance const user: UserExt = { phoneNumber: '...' } will result in an error.
33

Really depends on what you're trying to do. Types are documentation in typescript, so you want to show intention about how this thing is supposed to be used when you're creating the type.

Option 1: If Users might have some but not all of the attributes during their lifetime

Make all attributes optional

type User = {
  attr0?: number
  attr1?: string
}

Option 2: If variables containing Users may begin null

type User = {
...
}
let u1: User = null;

Though, really, here if the point is to declare the User object before it can be known what will be assigned to it, you probably want to do let u1:User without any assignment.

Option 3: What you probably want

Really, the premise of typescript is to make sure that you are conforming to the mental model you outline in types in order to avoid making mistakes. If you want to add things to an object one-by-one, this is a habit that TypeScript is trying to get you not to do.

More likely, you want to make some local variables, then assign to the User-containing variable when it's ready to be a full-on User. That way you'll never be left with a partially-formed User. Those things are gross.

let attr1: number = ...
let attr2: string = ...
let user1: User = {
  attr1: attr1,
  attr2: attr2
}

3 Comments

Not sure if this is what he is going for or not, but it is possible that we are talking about a seperate argument called UserAttributes or something that handles incoming parameters. It might not be an acceptable user object, and thus it could be defined seperately.
This should not be the accepted answer. Please see Shaun Luttin's post below.
@thargenediad why? It breaks type consistency in a way not intended by the language. If a downstream dev calls user01.name, they'll get a runtime error which TypeScript should prevent. It's a classic XY problem-- OP is really asking how to initialize an object, but they're asking about a solution they've come up with instead of asking the root question. Creating a partial object as suggested by unflores could also work well here.
19

Don't use {} as a type. {} actually means "any non-nullish value".

  • If you want a type meaning "empty object", you probably want Record<string, never> instead.
  • If you want a type meaning "any object", you probably want object instead.
  • If you want a type meaning "any value", you probably want unknown instead.

1 Comment

15

Note that using const user = {} as UserType just provides intellisense but at runtime user is empty object {} and has no property inside. that means user.Email will give undefined instead of ""

type UserType = {
    Username: string;
    Email: string;
}

So, use class with constructor for actually creating objects with default properties.

type UserType = {
  Username: string;
  Email: string;
};

class User implements UserType {
  constructor() {
    this.Username = "";
    this.Email = "";
  }

  Username: string;
  Email: string;
}

const myUser = new User();
console.log(myUser); // output: {Username: "", Email: ""}
console.log("val: "+myUser.Email); // output: ""

You can also use interface instead of type

interface UserType {
  Username: string;
  Email: string;
};

...and rest of code remains same.


Actually, you can even skip the constructor part and use it like this:

class User implements UserType {
      Username = ""; // will be added to new obj
      Email: string; // will not be added
}

const myUser = new User();
console.log(myUser); // output: {Username: ""}

Comments

13

In my case Record<string, never> helps, it was recommended by eslint Eslint recommendations

2 Comments

However if I have a union type of type Foo = { a: { b: string } } | Record<string, never> and I use it in the function parameter, function bar(foo: Foo) { console.log(foo.a.b) } will not error.
6
user: USER

this.user = ({} as USER)

1 Comment

Add some explanation to your answer please
3

you can do this as below in typescript

 const _params = {} as any;

 _params.name ='nazeh abel'

since typescript does not behave like javascript so we have to make the type as any otherwise it won't allow you to assign property dynamically to an object

1 Comment

If you use as any you might as well not use TypeScript. Never do this.
1

What i wanted is intellisense help for a chain of middlewares. Record<string, never> worked for me.

type CtxInitialT = Record<string, never>;
type Ctx1T = CtxInitialT & {
  name: string;
};
type Ctx2T = Ctx1T & {
  token: string;
};

cont ctx: CtxInitialT = {};
// ctx.name = ''; // intellisense error Type 'string' is not assignable to type 'never'
cont ctx1: Ctx1T = middleware1AugmentCtx(ctx);
// ctx1.name = 'ddd'; // ok
// ctx1.name1 = ''; // intellisense error Type 'string' is not assignable to type 'never'
cont ctx2: Ctx2T = middleware2AugmentCtx(ctx1);
// ctx2.token = 'ttt'; // ok
// ctx2.name1 = ''; // intellisense error Type 'string' is not assignable to type 'never'

Comments

1

If you want TS to understand that all references to an potentially empty object below a certain check are indeed that full type and not the unset empty object, you likely want an emptiness check that determines if the value is Record<string, never>,

function isEmpty<T>(x: T | Record<string, never>): x is Record<string, never> {
  return  x != null &&
  typeof x === 'object' &&
  Object.keys(x).length === 0
}

A common problem here is when JS logic has determined a nested object, usually in state, has been set, but TS complains, e.g.

import { isEmpty } from 'ramda'
// typed as (x: any) => boolean <--- boolean needs to be narrowed

const [user, setUser] = useState({})

...

const Dashboard = ({user}) => {

if(isEmpty(user)) return null 

user.address.city // <--- city is there, but TS complains 

If you type your object as T | Record<string, never> and write an emptiness check like the one above that types the return as value is Record<string, never>, TS will not complain in the above code and recognizes the entire user to as set.

Emptiness checks are usually generalized, so here are some other types that might be helpful in checking empty objects, arrays, or strings.

type Empty<T> = 
  T extends Array<infer U> ? [] : 
  T extends object ? Record<string, never> : 
  T extends string ? '' : 
  never

type MaybeEmpty<T> = T | Empty<T>

Note the return type for an empty array is x is never[], but I find that getting TS to understand emptiness is usually just an issue with objects.

Comments

0

If you declare an empty object literal and then assign values later on, then you can consider those values optional (may or may not be there), so just type them as optional with a question mark:

type User = {
    Username?: string;
    Email?: string;
}

Comments

-1

In case of

type User = {
    Username?: string;
    Email?: string;
}

you can declare an empty object or only one field(for example, only Username); in that case, you will not have an error, which can be a problem.

I will prefer more {} as User version.

2 Comments

It's bad practice to use optional parameters and optional stuff.
Call me crazy but isn't this exactly similar to @CatalinBerta 's solution which was answered 3 years before yours?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.