-3

We ran into the issue that a property was undefined even though the type doesn't allow it. Turns out the way we create the object in place has some issues. I made a small example which the linter accepts for some weird reason. I know how this can be fixed or should be used but want to be sure that there are and will be no traps like this in the future. It allows joeFriend.kidName to be undefined in spite of the cast to a type which doesn't allow it. If I remove the cast it knows that it can be undefined. If I remove "kidName" from that object it also allows it to be cast and created if I explicitly say that kidName:undefined it shows an error.

type person = {
    name: string;
    kid: { name: string; age: number } | undefined;
};

type friend = {
    name: string;
    kidName: string;
};

const joe = {
    name: "Joe",
    kid: undefined,
} as person;

const joeFriend = {
    name: joe.name,
    kidName: joe.kid?.name,
} as friend;

edit:

please disregard what I call cast or linter, you all know what I'm talking about. If this is just "compiler thinks you know better", why doesn't it allow this?

const joeFriend = {
    name: joe.name,    
    kidName: undefined,
} as Friend

any other language wouldn't allow this, all I asked is how would I be able to have strict check on this, nothing else

5
  • 1
    If you tell the compiler "be quiet, I know what I'm doing", why would you expect it to warn you that you did it wrong? "I made a small example which the linter accepts for some weird reason" it's not a linter, it's a compiler: a program that fails linting will still run (albeit with possibly unexpected behavior) but a "program" that doesn't compile isn't actually a valid program. Commented Oct 28, 2024 at 11:33
  • You shouldn't use type assertions (what you're calling a "cast") unless you want TypeScript to allow possibly-unsafe things. Instead you should annotate the type as shown in this playground link. Does that fully address the question? If so then I'd write an answer (but most of this is subsumed in the answer by Harrison below). If not, please edit to describe what's missing. Commented Oct 28, 2024 at 12:10
  • 2
    "If this is just 'compiler thinks you know better'" no, you told it that you know better. That's what a cast is. "Any other language wouldn't allow this", no, again, you told the compiler via the cast that you know the type for reasons it can't prove and that is exactly how casts work in every other language that offers them. If this was intended to be more of a "why does it work this way" question, please edit for tone and I will delete this. But if you blame the tool be very sure of your ground first. Commented Oct 28, 2024 at 12:36
  • • "please disregard what I call cast or linter, you all know what I'm talking about" Or you could edit so that we don't have to disregard misleading or confusing wording in the question. Or we could edit for you, would that be okay? • I'm confused about what the question is supposed to be after your edit. Could you please edit to make sure you're asking a single well-defined question whose scope is fixed?. Commented Oct 28, 2024 at 13:19
  • (see prev comment) If you're asking why some type assertions are allowed and others are not, it's because TS allows widenings ("upcasts", generally safe) or narrowings ("downcasts", generally unsafe), much like Java. It does not allow asserting to a type that is neither a supertype nor a subtype ("sidecasts"). Of course you can cast anything to/from unknown, so x as unknown as T will even allow sidecasting, since it is an upcast followed by a downcast. Does that fully address the question now? If so I'll answer (or more likely close as a duplicate). If not, please edit to clarify. Commented Oct 28, 2024 at 13:21

1 Answer 1

2

You are getting this issue because you are casting the type. (See Type Assertions)

Reminder: Because type assertions are removed at compile-time, there is no runtime checking associated with a type assertion. There won’t be an exception or null generated if the type assertion is wrong.

TypeScript is assuming you know better when making asserting the type with as. You code doesn't upset TypeScript (but it should)

type Person = {
    name: string;
    kid: { name: string; age: number } | undefined;
};

type Friend = {
    name: string;
    kidName: string;
};

const joe = {
    name: "Joe",
    kid: undefined,
} as Person

const joeFriend = {
    name: joe.name,
    kidName: joe.kid?.name,
} as Friend

Note: I declared the types with PascalCase just for readability, as IDEs highlight the syntax better.

This however will throw the error as the joeFriend is not correctly set up to be the Friend type:

const joe: Person = {
    name: "Joe",
    kid: undefined,
}

const joeFriend:Friend = {
    name: joe.name,
    kidName: joe.kid?.name,
}

Playground

Type 'string | undefined' is not assignable to type 'string'.
  Type 'undefined' is not assignable to type 'string'.(2322)
input.tsx(8, 5): The expected type comes from property 'kidName' which is declared here on type 'Friend'

Solution

You likely need to define Person and Friend in a more generic/reusable way such that any Person could also be a Friend, and only particular properties on Friend will allow you to check that the person is Friend, see Type Predicates

Perhaps something like:

type Person = {
  name: string,
  age: number
  kid: undefined
}

type Friend = Omit<Person, "kid"> & {
  kid: Person
}

const personA:Person = {
  name: "Bobby",
  age: 1,
  kid: undefined,
}

const personB: Friend = {
  name: "Joe",
  age: 1,
  kid: personA,
}

const personC: any = {
  name: "Alice",
  age: 1,
  kid: undefined,
}

const isFriend = (person: Person | Friend): person is Friend => person.kid != null

if (isFriend(personC)) {
  console.log("personC is friend", personC.kid)
} else {
  console.log("personC is not friend")
}

Playground
Note: I had to use any as Typescript was being too smart and recognising personC as Person type and not potentially as Friend type.

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

4 Comments

Thank you for the help. We have a big codebase and want to avoid these issues if they are used. We have the same fix for it which you mentioned (having the type for the const and not casting the object), but I want to make the dev environment safer, strict. As you said "it should" give an error and what I'm looking for is a setting that either the vscode or eslint would throw an error on this.
Well if the as keyword is being used, the IDE will just ignore it. Could you share your eslint and tsconfig files? Alternatively I have seen people use ts-ignore or ts-no-check in the code or in the config
it doesn't ignore it in this case: const joeFriend = { name: joe.name, kidName: undefined, } as Friend
Yes but you (the programmer) are forcing TypeScript to ignore the issue by using as. TypeScript is thinking you know better than it, you can't then expect TS to still throw the error...you've deliberately asked it not to... Your use-case is a reason to not cast the type; you would be better served resolving the actual issues in the data structures and expected types in your system.

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.