0

Let's say I have the object:

const styles = { product: { color: 'blue' } };

and with that the object type is inferred and I can access foo.bar.baz without a problem.

However, if I wanted type checking, I would need something like:

import type CSS from 'csstype';
type StyleSheet = Record<string,CSS.Properties>;

const styles:StyleSheet = {
    product: { color: 'blue' }
};

And the above would be valid, however, when accessing styles I would get styles.whatever.color or styles.product.margin as defined, which is undesired.

With that in mind, is there any way in Typescript to infer the original type of the object after it has been typed, so I can access its real properties, like so:

const styles:StyleSheet = { product: { color: 'blue' } }; // typed as 'StyleSheet' so I get type-checking
styles.whatever.margin // TS thinks this is valid because of the index signature;

const original = styles as OriginalObject; // infer original type so only real properties are accessible
original.whatever // invalid (expected)
original.product.color // valid

export default original;

Update

replying @jcalx's:

Playground link

1 Answer 1

1

You want to check that styles is a StyleSheet without widening styles to StyleSheet and having the compiler forget about its individual properties.


One way to do this is just let the compiler infer the type for styles and trust TypeScript's structural type system to complain if you use it somewhere it doesn't like. That is, assuming you have some function that requires a StyleSheet:

function someFunctionThatNeedsAStyleSheet(s: StyleSheet) { }

You can just make some unannotated objects:

const styles = { product: { color: 'gray' } };
const badStyles = { product: { colour: 'grey' } };

The compiler will remember their properties:

styles.whatever.margin // error;
styles.product.color // okay

And later, when you pass them to the function, it will either accept it or warn you:

someFunctionThatNeedsAStyleSheet(styles); // okay
someFunctionThatNeedsAStyleSheet(badStyles); // error!
// ----------------------------> ~~~~~~~~~
// Type '{ colour: string; }' has no properties in common 
// with type 'Properties<string | 0>' 

You do get type checking here. But of course, it's not in the same place where you declare the styles variable.


If you want to catch a bad StyleSheet right when you create it, you can use the above type-checking-function idea by making a helper function like this:

const asStyleSheet = <T extends StyleSheet>(t: T) => t;

The function asStyleSheet() is a generic identity function: it returns its input, with the same type as the input. But the generic type is constrained to StyleSheet, so you'll get an error if you pass in a bad one. Like this:

const styles = asStyleSheet({ product: { color: 'gray' } }); // okay
const badStyles = asStyleSheet({ product: { colour: 'grey' } }) // error!
// ---------------------------------------> ~~~~~~~~~~~~~~
// 'colour' does not exist in type 'Properties<string | 0>'. 
// Did you mean to write 'color' ? 

By using asStyleSheet(), you are checking the type of styles without changing it; the output of asStyleSheet() is the same type as the input. So the compiler remembers its properties:

styles.whatever.margin // error;
styles.product.color // okay

Looks good.


Okay, hope that helps; good luck!

Playground link to code

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

2 Comments

Thanks so much! That's great. There's only one thing, though: On the second example, why const styles = asStyleSheet({ product: { color: 'gray', foo:'bar' } }) doesn't show any error as foo is not a property os CSS.Properties while const s:StyleSheet = {product:{color: 'gray', foo: 'bar'}}; is invalid as expected?
Hey @jcalz, your response here: stackoverflow.com/a/61811167/165750 guided me to produce this (Playground link to code updated on the questions as it's too long for a comment). Let me know if you believe that's a good way of implementing it? Thanks for your answers around SO.

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.