Part 2: getting keys by value type

Looking up all numbers

Let’s solve an “easier” problem first: getting all the keys from our class TModel that have values of type number . I put “easier” in quotes here because the type alias that does this may still look a little daunting:

Making it more generic

We can generalize our NumberKeys type alias to get the keys of any given type:

type KeysOfType<TTarget, TValue> = {

[K in keyof TTarget]: TTarget[K] extends TValue ? K : never

}[keyof TTarget];

Great! So now all we need is to use this type alias to filter by the type of our special properties, and we’re done! 🥳

Well… therein lies the problem 😟. Our properties foo and bar may have special decorator, but their types are just regular number and Array<string> . And (at the time of writing) TypeScript doesn’t let you mutate the type of something with a decorator. It has a lengthy issue on Github, and it seems like this isn’t going to be resolved anytime soon. This leaves us with no choice but to explicitly change the types of our decorated properties.

Part 3: creating a “Special” type

Let’s create a new type Special that has some property on it that makes it distinguishable from other types. We can make something up, as we’re not really going to use it at runtime 🤷‍♂️. Let’s also modify our @special decorator such that it can no longer be used with properties that aren’t Special

Yes there is a bad pun. I regret nothing.

Here is a model that uses our new type:

Unfortunately we have introduced a new problem 😩. We can use still pass model.foo and model.bar to functions that expect number and Array<string> , but we can no longer update foo and bar with values that haven’t been cast to Special . This may be an acceptable trade-off when you define initial values on the class, but not very practical when using the model throughout your application.

This seems like a catch-22: we want to change the type of @special decorated properties so we can extract them, but every change makes them incompatible with our original types.

Part 4: back to the drawing board

Unacceptable?

So we started out with a method that was inadequate: putting the properties on a separate object. Then we moved to trying to extract our properties using special types. This seems to render our class completely useless. But what if we combined these to methods into one Frankenstein-like master hack? 🤔Surely, that will solve our issue.

🦹‍♂ ️Here is our evil master plan:

Define the @special decorated properties of our class with a Special type Use the Special type to extract the decorated properties on the class Create a new type that transforms all the decorated properties back to their original type but also adds a separate object with only the decorated properties Trick typescript into thinking that our original class is actually of this new type 👌

Our final model

This is what our final model will look like:

Note that the last line already contains step 4️⃣ of our evil masterplan. It exports FinalModel but with a slight modification. First, it makes TypeScript forget everything it knows about the class by casting it to the unknown. 🕶

“as unknown”

Second, we replace the type with our own interpretation. This is what as WithSpecial<typeof FinalModel> does. What I like about this final solution is that our model looks quite clean and readable. Sure we have to sprinkle in some Special typings in our class declaration, but our decorator signature will ensure that we don’t forget that. We still have to write WithSpecial though, which is where all dirty hacks will take place. So let’s hide that in a separate module where nobody will ever find it. 🤫

Transforming the class definition

So how do we transform a class definition? It comes down to 2 things: mapping over the static properties and transforming the constructor to make it return our modified instance type:

Transforming the class instance

Now lets define our new instance type. All properties Special properties will be mapped back to their original types. Additionally, we make up a new property called __special . This doesn’t really exist at runtime, but TypeScript will think it does. We can exploit this to still have access to all the names of the Special properties, even though the properties themselves are now back to normal.

The new and improved getSpecialProperty

Now that we have a class where our properties have normal types, and we have a util to extract all decorated properties, we can finally create our improved version of getSpecialProperty 😀

And with that error on qux , our search has come to an end. 🎉🎈🎊👯‍♂️

We have achieved our goal of type-checking for decorated properties, and we can now finally rest. 😴