1

I'm quite new to Typescript and work through a education video. Yesterday I found a weird behavior and think this is a bug. Example:

const json = '{"x": 10, "y":10}';
const coordinates: { x: number; y: number } = JSON.parse(json);

console.log(typeof coordinates.y);

This normally outputs x and y as type number. But it's not because of the type declaration, it's because of the JSON value. If you declare one of them as String, VS Code treats it like a String but internally it stays a number:

const json = '{"x": 10, "y":10}';
const coordinates: { x: number; y: string } = JSON.parse(json);

console.log(typeof coordinates.y);

This is the code:

The y variable is typed as a string

As you see, VS Code treats it as a string

The output of this is type number

But the type checking proves that this isn't correct.

In my opinion it makes sense that an Object/Array after parsing has an any type. but it should be either message you an error if the parsed JSON-value is different to your annotation or it should morph the given value exact to the annotation.

I'm quite new to this, so if my assumption is wrong, please let me know!

1
  • 4
    This is expected. TS doesn't do anything - you've declared your types some way and TS just takes your word for it. The compiler works before runtime, so it's impossible for TS to know what values there would be at runtime because TS doesn't exist then. So, the compiler only works with the information it knows - what you have said the types would be. Commented Feb 5, 2020 at 8:18

1 Answer 1

3

Typescript does inference before and during compilation. After compilation to JS, if some of your code's type logic is unsound, some of the types you've declared may be invalid. This is what's happening here.

JSON.parse returns something of the type any, which can be literally anything. You can try to extract properties from it with the line:

const coordinates: { x: number; y: string } = JSON.parse(json);

But this does not mean that the parsed object will actually have an x property as a number, or a y property as a string - your code there is telling the compiler to assume that it does, and to treat y as a string later in the code.

If you have something which is, by default, of type any, when extracting properties from it, you should make sure that its properties are really is of the type you're denoting them to be. If you're not 100% sure the properties will always be as expected (for example, if the input string comes from a network response - what if the response is 503?), make a type guard, such as:

const isCoordinates = (param: unknown): param is { x: number, y: number } => {
    return typeof param === 'object' && param !== null && 'x' in param;
};

Then call isCoordinates before trying to extract properties from the string to make sure it's of the format you expect first.

Otherwise, like what's happening here, you can tell the compiler something false, and bugs and strange things may happen as a result.

You may consider enabling the tslint rule no-unsafe-any to avoid making these sort of mistakes.

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

4 Comments

"Typescript does inference before and during compilation." we should really have some canonical on this. I see a lot of people assuming they are in some way "running TypeScript code" as if types exist in runtime.
Also, just to add to this answer - aside from a type guard, you can also employ code that takes in the data and tries to make sure it's correct. So, you could do something like data => { return { x: Number(data.x), y: Number(data.y)} } so you produce something in the correct shape and correct types. Of course there could add more logic like defaulting values if they don't exist but at least at the end you know you have something that fits your type.
Hmm ok, that means it makes no sense to spread a parsed Json Object with an explicit type woithout checking if its valid. For me personally it is quite wrong because i shouldn't be possible do declare anything thats not treated in the background like that. In my opinion thes should throw an error when a string is not an actual string. even if its from any type. But thanks for the explanation! I'll keep it in mind.
@ChrisPi Right. If you want to be type-safe, and you aren't 100% sure that a parsed JSON will result in a particular shape, use a type guard (or something else that validates the type). Typescript cannot infer the type of the parsed JSON by itself, since it's only something that occurs at runtime, not at compile-time, so it relies on hints from the script-writer to understand the shape of it.

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.