How To Strongly Type process.env
Learn how to strongly type process.env in TypeScript by either augmenting global type or validating it at runtime with t3-env.
The empty object type - {}
- doesn't behave how you expect in TypeScript.
Instead of representing an empty object, it represents any value except null
and undefined
.
This is because TypeScript's type system is structural, not nominal. Everything except null
and undefined
is an object, so everything can be assigned to an empty object.
If you want to represent an empty object, use Record<string, never>
instead.
The empty object type in TypeScript doesn't really behave as you expect. It doesn't represent "any object". Instead, it represents any value that isn't null
or undefined
.
Try experimenting with it in the playground below:
Here, we are basically typing example1
as an empty object, yet we can pass a string to it. We can pass a number to it. We can pass a boolean to it and any object to it.
The only things we can't pass to it are null
or undefined
:
ts
constType 'null' is not assignable to type '{}'.2322Type 'null' is not assignable to type '{}'.: {} = null; example5 constType 'undefined' is not assignable to type '{}'.2322Type 'undefined' is not assignable to type '{}'.: {} = example6 undefined ;
If you're using ESLint, you might even hit an error:
Don't use
{}
as a type.{}
actually means "any non-nullish value".
This article explains exactly why this is good advice.
Object
TypeThis also happens with the Object
type, which I think is just an alias over the top of {}
:
ts
constobj1 :Object = "str";constType 'null' is not assignable to type 'Object'.2322Type 'null' is not assignable to type 'Object'.: obj2 Object = null;
So this behaves in exactly the same way. So you shouldn't be using this Object
type either.
If you do want to represent a kind of empty object, then we can use this Record<PropertyKey, never>
.
ts
typeEmptyObj =Record <PropertyKey , never>;constemptyObj1 :EmptyObj = {};constemptyObj2 :EmptyObj = {Type 'string' is not assignable to type 'never'.2322Type 'string' is not assignable to type 'never'.: "whatever", foo };constType 'string' is not assignable to type 'EmptyObj'.2322Type 'string' is not assignable to type 'EmptyObj'.: emptyObj3 EmptyObj = "str";constType 'number' is not assignable to type 'EmptyObj'.2322Type 'number' is not assignable to type 'EmptyObj'.: emptyObj4 EmptyObj = 123;
What this does is it means that you can pass an empty object. It's going to stop you from passing anything with a property on it. And it's also going to stop you from passing primitives, or null or undefined.
The only time this might be useful is if you want a really wide constraint on a function. Let's say you want to make sure that the thing that you're passing that function isn't null
or undefined
:
ts
constmyFunc = (constraint : {}) => {};myFunc ("str");myFunc (123);myFunc (true);
But even then, you're not going to be able to access any properties on constraint
:
ts
constmyFunc = (constraint : {}) => {Property 'foo' does not exist on type '{}'.2339Property 'foo' does not exist on type '{}'.constraint .; foo };
You'll hit this error:
Property 'foo' does not exist on type ''.
This is happening because we're trying to access a property on an empty object. So it's not terribly useful.
The only possible place it could be useful - or at least the one I want to cover here - is as a constraint in a generic function.
ts
constmyGenericFunc = <T extends {}>(t :T ) => {returnt ;};constresult1 =myGenericFunc ("str");constresult2 =myGenericFunc (123);constresult3 =myGenericFunc (true);
Inside myGenericFunc
, we want to make sure that we can't pass null
or undefined
into the generic function. If we hover over myGenericFunc
here, you can see that it's capturing str
and returning it in the results here.
But it fails when we try to pass in null or undefined.
ts
constArgument of type 'null' is not assignable to parameter of type '{}'.2345Argument of type 'null' is not assignable to parameter of type '{}'.result4 =myGenericFunc (null );constArgument of type 'undefined' is not assignable to parameter of type '{}'.2345Argument of type 'undefined' is not assignable to parameter of type '{}'.result5 =myGenericFunc (); undefined
So, you probably shouldn't be using the {}
type really anywhere. And you'll have likely come to this article because you found a linting rule that's preventing you from using it. That is why it doesn't represent what you think, but it's occasionally useful for constraining generics.
Got any more questions? Found any more use cases? Let me know:
Share this article with your friends
Learn how to strongly type process.env in TypeScript by either augmenting global type or validating it at runtime with t3-env.
Discover when it's appropriate to use TypeScript's any
type despite its risks. Learn about legitimate cases where any
is necessary.
Learn why TypeScript's types don't exist at runtime. Discover how TypeScript compiles down to JavaScript and how it differs from other strongly-typed languages.
Improve React TypeScript performance by replacing type & with interface extends. Boost IDE and tsc speed significantly.
In this book teaser, we discuss deriving vs decoupling your types: when building relationships between your types or segregating them makes sense.
Learn how TypeScript's new utility type, NoInfer, can improve inference behavior by controlling where types are inferred in generic functions.