Why does the in operator return true for a property that is undefined?
Note: While researching the question I found the answer, since I have already spent the time, I decided to post what I found.
The reason why they return a different value is that [[GetProperty]]
calls [[GetOwnProperty]]
, which does does not return the property value. [[GetOwnProperty]]
returns a Property Descriptor which describes things like if the property is enumerable, writable and it's value.
When you set a property to undefined
as with bar
in the question, a Property Descriptor is created* with a [[Value]]
of undefined
. When [[GetOwnProperty]]
is called for bar
the Property Descriptor is returned, while with baz
, undefined
is returned. This means that [[GetProperty]]
return a defined value for bar
, which then makes [[HasProperty]]
return true
, instead of false
. Which in turn makes the in
operator return true
as well.
* this is a lie, but for simplicity sake.
Specification Trace
From Annotated ECMAScript 5.1.
Note: Numbers below link to Spec reference where possible
For: 'bar' in foo
- 1. Evaluating
'bar'
gives a string'bar'
- 2.
GetValue
of'bar'
will return the value'bar'
- 3. Evaluating
foo
gives a referencefoo
- 4.
GetValue
offoo
will return the value of'foo'
, our object - 5.
Type
of value offoo
is an object, no exception thrown toString
of value of'bar'
returns the string'bar
, call[[HasProperty]]
- 1.
[[GetProperty]]
returns Property Descriptor (notundefined
) - 1.
[[GetOwnProperty]]
return Property Descriptor (notundefined
) foo
does have own property calledbar
, do not returnundefined
- Create a Property Descriptor with no fields
- Create X from
foo.bar
- Create X from
- X is a data property, so set [[Value]] to
undefined
and set[[Writable]]
- X is a data property, so set [[Value]] to
- Else branch skipped
- Set [[Enumerable]]
- Set [[Configurable]]
- Return Property Descriptor
- Property Descriptor is not undefined, return the Property Descriptor.
- Property Descriptor is not
undefined
, so don't return false
- Property Descriptor is not
- else, return true
- 1.
Is it possible to have class.property = x return something other than x?
One downside is that you would break the chained assignment semantics:
$ irb
irb(main):001:0> x = y = 3
=> 3
irb(main):002:0> p x
3
=> nil
irb(main):003:0> p y
3
=> nil
irb(main):004:0>
Consider:
x = MyClass.property = 3
Then x
would take true
if this worked as you had expected (right-associativity). That could be a surprise for people using your interface and used to the typical semantics.
You also got me thinking about parallel assignment, eg:
x, y = 1, 2
Apparently the return value from that expression is implementation specific... I guess I won't be chaining parallel assignments :)
Nice question!
TypeScript function that returns arg with extra property (TS2322)
Forging object literals with spread syntax and getting the types in line can be tricky in TypeScript. Assuming x
does not change, you can simplify the problem a bit by not extracting x
and adding it back to the result, but just destructure props
separately instead:
function addY<T extends { x: number }>(props: T): Omit<T, 'y'> & { y: number } {
const {x} = props
return {
...props,
y: x + 1,
};
}
This type checks, and if you define a function Normalize
to get rid of type aliases
type Normalize<T> = {[K in keyof T]: T[K]}
you can check that the return types are as expected:
const o1 = addY({x:10, z: 1})
type TestO1 = Normalize<typeof o1> // {x: number, z: number, y: number}
const o2 = addY({x:10, z: 1, y: 'str'})
type TestO2 = Normalize<typeof o2> // {x: number, z: number, y: number}
Of course, you could also use props.x
instead of destructuring.
TypeScript playground
Checking if object has property with given name returns false
You have three objects and the methods you are using to test are meant for a single object.
obj
- object containing a games propertyobj.games
- array with one elementobj.games[0]
- object with id property
Consider these which all are true:
let obj = {
games: [
{id: 'test'}
]
}
console.log(`obj.hasOwnProperty('games')`, obj.hasOwnProperty('games'))
console.log(`'games' in obj`, 'games' in obj)
console.log(`obj.games.hasOwnProperty('0')`, obj.games.hasOwnProperty('0'))
console.log(`'0' in obj.games`, '0' in obj.games)
console.log(`obj.games[0].hasOwnProperty('id')`, obj.games[0].hasOwnProperty('id'))
console.log(`'id' in obj.games[0]`, 'id' in obj.games[0])
if (obj && obj.games && obj.games.length > 0 && obj.games[0].id) {
console.log('obj.games has at least one element, '
+ 'and the first element has an id')
}
let id = obj && obj.games && obj.games.length > 0 && obj.games[0].id;
console.log('id:', id);
Property does not exist on type void
You don't return anything from your catch block, so the return type of your function is Promise<WhateverTheTypeOfResponseIs | void>
(N.B. async
functions implicitly return a Promise, and if your postWithToken
function doesn't return anything then it's just Promise<void>
), depending on which code path happens.
In the future you can avoid unpleasant and slightly problematic to debug issues like this by giving your functions an explicit return type and in this case the compiler will let you know that your expectation was violated:
const postWithToken = async (route: string, token: string, obj: any): Promise<boolean> => {
try {
const resp = await fetch(
route,
{
method: 'POST',
body: JSON.stringify(Object.assign(obj, { token }))
},
)
return Boolean(resp.status < 400 && resp.status >= 200)
} catch (err) {
console.error(err)
return false
}
}
const API_ROUTES = {
PAYMENT_SESSION: 'whatever'
}
const testObject = {
token: ''
}
const token = ''
const createOrder = async (): Promise<boolean> => { // <-- squiggles
try {
const response = await postWithToken(API_ROUTES.PAYMENT_SESSION, token || '',
testObject)
console.log("FROM HOOK", response)
return response
} catch (err: any) {
console.log(err)
}
}
Playground
The types in the example I created may be different (you'll need to sub with the actual types from your code) but you should get the idea. You can fix this by any of the following:
- Explicitly return something of the correct type from the catch block.
- Change your return type to
Promise<CorrectType | undefined>
. - Move the error handling to the caller as suggested by goto1 in the comments.
Also note that as goto1 points out in the comments on the question, your hook isn't actually a hook (which is fine, but be careful of terminology).
Interface is allowing extra property when it is used as return type of a function
An inteface seems to accept extra property(ies) when it is assigned as a return of a function via a type.
In general, TypeScript uses structural typing, so it is perfectly fine to assign an object with additional properties to a Human
interface.
const lui = {
name: "Lui",
age: 40
}
const human: Human = lui // works,
You can assign lui
to variable human
with type Human
, because typeof lui
is a sub-type and therefore has the same/compatible property members.
An exception to this rule are excess property checks for "freshly created object literals", that are intended as developer aid and prohibit extra properties to be added. The general thought here is, that you exactly know what properties you want, when you define a direct object literal with no other indirection (access of variables etc. to get that object value).
Excess property checks require an immediately following explicit type annotation at the variable, property or function dealing with the fresh object literal in order to work. Otherwise the object literal type doesn't count as "fresh" anymore (its type is widened). Let's check your examples to illustrate the concept.
const humanCreator: HumanCreator = (name, age) => ({
name,
age // No error. Why?
});
You can see this as an assignment of a type compatible function to the variable with type HumanCreator
. The compiler looks at the function expression (name, age) => ({ name, age })
, infers parameter types and makes sure that its return type is compatible to the variable type. And indeed - {name: string; age:number}
return type is assignable to Human
(structural typing).
const humanCreatorr: HumanCreator = (name, age): Human => ({
name,
age // Error
});
const humanCreatorrr = (): Human => ({
name: '',
age: 0 // Error
});
These cases are different, as you immediately annotate the functions with Human
return type. There is no type inference for the return type necessary for the compiler. tl;dr To enable excess property checks, annotate an explicit type for your object literal as close as possible.
Further links
- Excess property checks in TS specifications
- Widened types
- Good introduction article about excess property checks
How to type functions that return properties of objects based on argument provided?
You want to make propname
's type a generic type parameter like K extends keyof OBJ
(well, T
, not OBJ
, because OBJ
is not a conventional type parameter name), and then use indexed access types to represent the property type: T[K]
.
This looks very close to the getProperty()
function shown in the (now deprecated) documentation for indexed access types:
function getProperty<T, K extends keyof T>(o: T, propertyName: K): T[K] {
return o[propertyName]; // o[propertyName] is of type T[K]
}
And you can verify that it works:
const name = getProperty(obj, 'name');
// const name: string
console.log(name.toUpperCase()); // "HELLO"
Playground link to code
Related Topics
Swift-Setting a Physics Body Velocity by Angle
Partial Application of 'Mutating' Method Is Not Allowed
How to Split a String into a [String] and Not [Substring]
Closure:Use Unresolved Identifier 'Self'
Getting Data Out of Nsdata with Swift
Ios: Ambiguous Use of Init(Cgimage)
Argument of '#Selector' Does Not Refer to an '@Objc' Method, Property or Initializer
Treat a Single Integer Value as a Range in Swift
How to Call a Selector-Based Timer Method on a Swift Struct
How to Make a Function Complete Before Calling Others in an Ibaction
How to Make a Segue to Second Item of Tab Bar
Firebase Crashlytics Not Showing Crash Report in Console Dashboard Swift
Codable Enum with Multiple Keys and Associated Values
Create a Navigationlink Without Back Button Swiftui
Swift/Cloudkit: After Record Changed, Upload Triggers "Service Record Changed"