JavaScript Object to Primitive Conversion

Now it’s time to find out what will happen if you add objects obj1 + obj2, subtract obj1 - obj2, or print using alert(obj). In such a case, objects will be auto-converted to primitives, after which the operation will be carried out. Here are the main rules for numeric, string, and boolean conversions of objects:

  • Overall objects are considered true in a boolean conversion. There are merely string and numeric conversions.
  • The numeric conversions take place when subtracting objects or applying mathematical functions.
  • String conversion happens when we output an object like alert(obj) and in contexts like that.

ToPrimitive

It is possible to enhance string and numeric conversion. To meet that goal, you need to use unique object methods.

We observe three variants of type conversion, also known as “hints” that are described in specification:

"string"

In case of an object-to-string conversion, while operating on an object which expects a string, like alert:

// output
alert(obj);

// using object as a property key
anotherObj[obj] = 120;

"number"

In case of an object-to-number conversion, like when you are doing mathematics:

// explicit conversion
let num = Number(obj);
// maths (except binary plus)
let n = +obj; // unary plus
let delta = obj1 - obj2;
// less/greater comparison
let greater = obj1 > obj2;

"default"

It happens rarely, in case the operator doesn’t certainly know which type to expect. For example, the binary + will work with both numbers and strings. If the binary + has an object as an argument, it will use the "default" for converting it.

When you compare object using == with a symbol, number, or a string, it is not clear which conversion is better to do. That’s why it is better to use the "default" hint.

// binary plus uses the "default" hint
let total = obj1 + obj2;

// obj == number uses the "default" hint
if (car == 1) { ...
};

The greater and less comparison operators <,> work with numbers and strings as well. But, note that in this case, the "number" hint is used, not the "default", as in the previous examples.

Anyway, in practice, you don’t need to remember all these details, almost all built-in objects ( the exception is the Date object ).

For implementing the conversion, JavaScript needs to find and invoke 3 object methods. They are as follows:

  • Call obj[Symbol.toPrimitive](hint) - a method including a symbolic key Symbol.toPrimitive. If there are such methods,
  • In other cases, when the hint is "string", keep on trying obj.toString() and obj.valueOf().
  • If the hint is "default" or "number", keep on trying obj.valueOf() and obj.toString().

Symbol.toPrimitive

The first thing that you should know is that a built-in method exists known as Symbol.toPrimitive. In general, you can use it for naming your conversion method as follows:

obj[Symbol.toPrimitive] = function (hint) {
  // must return a primitive value
  // hint = one of "string", "number", "default"
};

In the following example, car object applies it:

let car = {
  name: "BMW",
  price: 30000,
  [Symbol.toPrimitive](hint) {
    console.log(`hint: ${hint}`);
    return hint == "string" ? `{name: "${this.name}"}` : this.price;
  }
};
// conversions demo:
console.log(car); // hint: string -> {name: "BMW"}
console.log(+car); // hint: number -> 30000
console.log(car + 5000); // hint: default -> 35000

toString/valueOf

Now it’s time to learn about toString and valueOf methods. Don’t get surprised to find out that they are not considered as symbols. They are among the most ancient methods.

The primary purpose of these methods is to provide an “ancient-style” way to run the conversion.

In the event of not existing Symbol.toPrimitive JavaScript will try to find them in the following sequence:

  1. toString -> valueOf in case of “string” hint.
  2. valueOf -> toString in other cases

The methods mentioned above will bring a primitive value. Returning an object by valueOf or toString means that it is ignored.

An object includes toString and valueOf methods as follows:

  • "[object Object]" is returned by the method of toString.
  • The object itself will be returned by the valueOf method.

Let’s have a look at this case:

let site = {
  name: "W3Docs"
};
console.log(site); // [object Object]
console.log(site.valueOf() === site); // true

Therefore, anytime using an object as a string, you will have [object Object].

The Types of Return

First and foremost, you need to note that primitive-conversion methods never return the primitive that was hinted. You can’t control whether toString method returns a string or Symbol.toPrimitive returns "number".

One thing is compulsory: the methods mentioned above have to return a primitive and never an object.

Further Conversions

You have already learned that a wide range of operators and functions implement type conversions. For example, multiplying * will convert operands into numbers.

In the event of passing an object as an argument, two stages can be distinguished:

  • The object has been converted to a primitive.
  • In case the resulting primitive isn’t of the proper type, it is converted.

Let’s have a look at this example:

let obj = {
  //toString is capable of handling all conversions in the absence of different methods
  toString() {
    return "2";
  }
};
console.log(obj * 3); // 6, the object is converted to primitive "2", after which a number is made by multiplication

Here, as the first step, the object is converted to a primitive by the multiplication obj * 3. Afterward, "2" * 3 is transformed into 2 * 3.

Strings will be concatenated in the same condition by the binary plus as it accepts a string. Here is an example:

let obj = {
  toString() {
    return "2";
  }
};
console.log(obj + 3); // 23 ("2" + 3), the object is converted to primitive  returned a string => concatenation

Summary

The conversion from object to primitive can be invoked automatically by a range of built-in functions and operators expecting primitive as a value.

It has the following three hints:

  1. "string" used for alert as well as other operations that require a string;
  2. "number" (used for mathematics)
  3. "default"( not many operators)

The algorithm of the conversion is as follows:

  1. Run obj[Symbol.toPrimitive](hint) in case there is a method
  2. In other cases, when the hint is "string" . run obj.toString() and obj.valueOf(), whichever exists
  3. In case the hint is "default"or "number" . run obj.valueOf() and obj.toString(), whichever exists.



Do you find this helpful?

Related articles