No Time Dad

A blog about web development written by a busy dad

TypeScript Readonly type for a Single Property

I’ve really only used TypeScript’s “everyday types” for most of my projects. Those with a few of the utility types has usually been enough for most of my use cases. I am, however, trying to dig deeper into TypeScript’s type system in an effort to become more comfortable creating complex custom types.

An interesting scenario I ran into recently was the need to extend an existing interface so that one of it’s properties was readonly. I looked through the utility types documentation, but the closest thing I could find was the TypeScript Readonly utility type.

The problem with the Readonly utility type is that would make all properties readonly. I only need one particular property to be readonly.

In this example, I have an interface called Product with three properties that are not readonly. I use this interface a lot throughout my app, but in a certain part of the app I want to make sure that the price property cannot be changed while the others can. Below is the first pass at solving this problem just using the Readonly utility type.

interface Product {
  name: string
  description: string
  price: number
}

const shoe: Readonly<Product> = {
  name: "Runners",
  description: "Awesome running shoes",
  price: 50.54,
}

// Looking good so far, cannot change the price
shoe.price = 100.25
// Cannot assign to 'price' because it is a read-only property.ts(2540)

// Oh, not good. I want to be able to change this value.
shoe.name = "Super runners"
// Cannot assign to 'name' because it is a read-only property.ts(2540)

// Again, bad.
shoe.description = "New description"
// Cannot assign to 'description' because it is a read-only property.ts(2540)

So, I need another approach. And since TypeScript doesn’t have a type for this out of the box I’m going to have to create my own.

Solution

My first pass solution is to use the TypeScript extends keyword to create a new interface that extends the Product interface. I can then overwrite the price property to include the readonly modifier.

interface ProductReadonlyPrice extends Product {
  readonly price: number
}

const shoe2: ProductReadonlyPrice = {
  name: "Runners",
  description: "Awesome running shoes",
  price: 50.54,
}

// Again, looking good so far...
shoe2.price = 100.25
// Cannot assign to 'price' because it is a read-only property.ts(2540)

// Success!
shoe2.description = "New description"
shoe2.name = "New runners"

And this works exactly how I want it to. But I think there might be better solutions for making a single property readonly. It would be nice if this was a little more dynamic. For example, passing in the property or multiple properties to make readonly.

There are some cool projects on GitHub where TypeScript’s type system is pushed to the limits. A lot of it is over my head right now, but I’m hoping to change that.

A few example repositories to look at: