# Object to primitive conversion What happens when objects are added `obj1 + obj2`, subtracted `obj1 - obj2` or printed using `alert(obj)`? In that case, objects are auto-converted to primitives, and then the operation is carried out. In the chapter we've seen the rules for numeric, string and boolean conversions of primitives. But we left a gap for objects. Now, as we know about methods and symbols it becomes possible to fill it. 1. All objects are `true` in a boolean context. There are only numeric and string conversions. 2. The numeric conversion happens when we subtract objects or apply mathematical functions. For instance, `Date` objects (to be covered in the chapter ) can be subtracted, and the result of `date1 - date2` is the time difference between two dates. 3. As for the string conversion -- it usually happens when we output an object like `alert(obj)` and in similar contexts. ## ToPrimitive We can fine-tune string and numeric conversion, using special object methods. There are three variants of type conversion, so-called "hints", described in the [specification](https://tc39.github.io/ecma262/#sec-toprimitive): `"string"` : For an object-to-string conversion, when we're doing an operation on an object that expects a string, like `alert`: ```js // output alert(obj); // using object as a property key anotherObj[obj] = 123; ``` `"number"` : For an object-to-number conversion, like when we're doing maths: ```js // explicit conversion let num = Number(obj); // maths (except binary plus) let n = +obj; // unary plus let delta = date1 - date2; // less/greater comparison let greater = user1 > user2; ``` `"default"` : Occurs in rare cases when the operator is "not sure" what type to expect. For instance, binary plus `+` can work both with strings (concatenates them) and numbers (adds them), so both strings and numbers would do. Or when an object is compared using `==` with a string, number or a symbol, it's also unclear which conversion should be done. ```js // binary plus let total = car1 + car2; // obj == string/number/symbol if (user == 1) { ... }; ``` The greater/less operator `<>` can work with both strings and numbers too. Still, it uses "number" hint, not "default". That's for historical reasons. In practice, all built-in objects except for one case (`Date` object, we'll learn it later) implement `"default"` conversion the same way as `"number"`. And probably we should do the same. Please note -- there are only three hints. It's that simple. There is no "boolean" hint (all objects are `true` in boolean context) or anything else. And if we treat `"default"` and `"number"` the same, like most built-ins do, then there are only two conversions. **To do the conversion, JavaScript tries to find and call three object methods:** 1. Call `obj[Symbol.toPrimitive](hint)` - the method with the symbolic key `Symbol.toPrimitive` (system symbol), if such method exists, 2. Otherwise if hint is `"string"` - try `obj.toString()` and `obj.valueOf()`, whatever exists. 3. Otherwise if hint is `"number"` or `"default"` - try `obj.valueOf()` and `obj.toString()`, whatever exists. ## Symbol.toPrimitive Let's start from the first method. There's a built-in symbol named `Symbol.toPrimitive` that should be used to name the conversion method, like this: ```js obj[Symbol.toPrimitive] = function(hint) { // must return a primitive value // hint = one of "string", "number", "default" }; ``` For instance, here `user` object implements it: ```js run let user = { name: "John", money: 1000, [Symbol.toPrimitive](hint) { alert(`hint: ${hint}`); return hint == "string" ? `{name: "${this.name}"}` : this.money; } }; // conversions demo: alert(user); // hint: string -> {name: "John"} alert(+user); // hint: number -> 1000 alert(user + 500); // hint: default -> 1500 ``` As we can see from the code, `user` becomes a self-descriptive string or a money amount depending on the conversion. The single method `user[Symbol.toPrimitive]` handles all conversion cases. ## toString/valueOf Methods `toString` and `valueOf` come from ancient times. They are not symbols (symbols did not exist that long ago), but rather "regular" string-named methods. They provide an alternative "old-style" way to implement the conversion. If there's no `Symbol.toPrimitive` then JavaScript tries to find them and try in the order: - `toString -> valueOf` for "string" hint. - `valueOf -> toString` otherwise. For instance, here `user` does the same as above using a combination of `toString` and `valueOf`: ```js run let user = { name: "John", money: 1000, // for hint="string" toString() { return `{name: "${this.name}"}`; }, // for hint="number" or "default" valueOf() { return this.money; } }; alert(user); // toString -> {name: "John"} alert(+user); // valueOf -> 1000 alert(user + 500); // valueOf -> 1500 ``` As we can see, the behavior is the same as the previous example with `Symbol.toPrimitive`. Often we want a single "catch-all" place to handle all primitive conversions. In this case, we can implement `toString` only, like this: ```js run let user = { name: "John", toString() { return this.name; } }; alert(user); // toString -> John alert(user + 500); // toString -> John500 ``` In the absence of `Symbol.toPrimitive` and `valueOf`, `toString` will handle all primitive conversions. ## Return types The important thing to know about all primitive-conversion methods is that they do not necessarily return the "hinted" primitive. There is no control whether `toString()` returns exactly a string, or whether `Symbol.toPrimitive` method returns a number for a hint "number". The only mandatory thing: these methods must return a primitive, not an object. ```smart header="Historical notes" For historical reasons, if `toString` or `valueOf` returns an object, there's no error, but such value is ignored (like if the method didn't exist). That's because in ancient times there was no good "error" concept in JavaScript. In contrast, `Symbol.toPrimitive` *must* return a primitive, otherwise there will be an error. ``` ## Further operations An operation that initiated the conversion gets the primitive, and then continues to work with it, applying further conversions if necessary. For instance: - Mathematical operations, except binary plus, convert the primitive to a number: ```js run let obj = { // toString handles all conversions in the absence of other methods toString() { return "2"; } }; alert(obj * 2); // 4, object converted to primitive "2", then multiplication made it a number ``` - Binary plus will concatenate strings in the same situation: ```js run let obj = { toString() { return "2"; } }; alert(obj + 2); // 22 (conversion to primitive returned a string => concatenation) ``` ## Summary The object-to-primitive conversion is called automatically by many built-in functions and operators that expect a primitive as a value. There are 3 types (hints) of it: - `"string"` (for `alert` and other operations that need a string) - `"number"` (for maths) - `"default"` (few operators) The specification describes explicitly which operator uses which hint. There are very few operators that "don't know what to expect" and use the `"default"` hint. Usually for built-in objects `"default"` hint is handled the same way as `"number"`, so in practice the last two are often merged together. The conversion algorithm is: 1. Call `obj[Symbol.toPrimitive](hint)` if the method exists, 2. Otherwise if hint is `"string"` - try `obj.toString()` and `obj.valueOf()`, whatever exists. 3. Otherwise if hint is `"number"` or `"default"` - try `obj.valueOf()` and `obj.toString()`, whatever exists. In practice, it's often enough to implement only `obj.toString()` as a "catch-all" method for all conversions that return a "human-readable" representation of an object, for logging or debugging purposes.