I have a Typescript application that, over the course of a series of functions, builds up a complex object with many properties. The object starts off with few properties, and ends with many.
As an example of a function that adds a property to such an object:
interface ComplexObject {
name: string
size: number
shape: string
numberOfCorners?: number
internalAngle?: number
/*... more optional properties */
}
function computeProperties (shape: ComplexObject): void {
countCorners(shape)
calculateAngle(shape)
/*... more functions that add properties */
}
function countCorners (shape: ComplexObject): void {
if (shape.shape === 'square') {
shape.numberOfCorners = 4
} else {
throw new Error()
}
}
function calculateAngle (shape: ComplexObject): void {
// I can guarantee that numberOfCorners exists, but Typescript
// doesn't know that, so I have to make a non-null assertion (!)
shape.internalAngle = 360 / shape.numberOfCorners!
}
const myObject: ComplexObject = { name: "Square", size: 20, shape: 'square' }
computeProperties(myObject)
Let's say that there are many different properties to add to this object, and that they are not guaranteed to be added in any particular order.
In order to avoid having to write an interface for each and every permutation of ComplexObject, I've made one long interface with the guaranteed starting properties, and every possible added property as an optional property.
However, I'm now finding myself repeatedly having to assert that a property exists - e.g. shape.numberOfCorners! - because while I can guarantee that an object passed to a given function has a given property, Typescript doesn't know this, because I made them all optional.
Best practice would indicate that at any point I should be checking to see if a property exists and throwing an error accordingly. But I know my own code, I know there are only so many pathways through it, and I know that that isn't necessary. The Typescript compiler should be making that approach obsolete by confirming that these incorrect pathways do not exist.
I feel like I'm working to avoid Typescript, when it should be working for me.
My question: How can I inform Typescript that, for a given function, the object passed to it will have a given set of properties? How can I have Typescript inform me when I pass an object to this function that does not have those properties?
Critically, how can I do so with as little repetition as possible?
shape[someUnknownProperty] = valuein this case. My functions are all nested in one long chain with a single entry point. For any one of those functions, I can assert that the object it takes as argument must have certain properties, and that it adds certain properties to the object, right? Perhaps I have to also return and reassign the modified object. Then the compiler should be able to tell me "you're trying to callcountCornersbut this object doesn't have theshapeproperty" - right?