#What is wrong with this narrowing?

9 messages · Page 1 of 1 (latest)

dusk rune
#
interface SomeObject {
    id: string;
    otherProp?: string;
}

const validate =
  (value: unknown) => {
    if (!value || typeof value !== "object" || !("id" in value) || typeof value.id !== "string") return;

    return {
      value: value satisfies SomeObject,
    };
  }

value does not satisfy SomeObject even that it is check if prop id exists and is string
https://tsplay.dev/WokBPW

vale sandalBOT
#
Frosk#6439

Preview:```ts
interface SomeObject {
id: string
otherProp?: string
}

const validate = (value: unknown) => {
if (
!value ||
typeof value !== "object" ||
!("id" in value) ||
typeof value.id !== "string"
)
throw new Error("Not SomeObject")
...```

kind summit
#

You can check the type as I did to see it is not being narrowed correctly, even though it seems all the checks are done right. A type guard is the tool you need in these situations

#

cleaned up a little:

vale sandalBOT
#
Frosk#6439

Preview:```ts
interface SomeObject {
id: string
otherProp?: string
}

const validate = (value: unknown) => {
if (!isSomeObject(value)) {
throw new Error("Not SomeObject")
}
return value
}

function isSomeObject(
value: unknown
): value is SomeObject {
return (
!!value &&
typeof value === "object" &&
!Array.isArray(value) &&
"id" in value &&
typeof value.id === "string"
)
...```

wicked light
#

@dusk rune I think a more direct answer here is that when you do typeof value.id === "string", that narrows value.id, but it does not narrow value to be { id: string }.

#

So this version works:

const validate =
  (value: unknown) => {
    if (!value || typeof value !== "object" || !("id" in value) || typeof value.id !== "string") return;

    return {
      value: { id: value.id } satisfies SomeObject,
    };
  }
#

This may seem weird at first glance: why value.id is narrowed, not value, but it makes sense if you consider something like:

const someVal: { id: string | number } = { id: "foo" };
const res = validate(someVal)!.value; 
someVal.id = 0;
res.id // type says string, but would actually be a number with the original version

With your original version, validate would return someVal as res, meaning that when someVal.id is set to 0 (a valid thing according to it's type), res.id would also be set to 0, which is invalid.

#

In other words, checking typeof value.id can only tell you that the current id is a string, not that id is always a string.