[logo] a small computer

The Mystery of Javascript's map and parseInt Turning to NaN.

the mystery of javascript's map and parseint turning to nan.

Bradley Kingsley
Published a year ago.
5 minute read
logo of undefined

Javascript can safely be regarded as the weirdest language in all of tech. Until recently, it's API has been quite shallow, especially when compared to popular languages such as Python and the more archaic Java.

For all its quirkiness, one thing about Javascript stands out head and shoulders as almost downright annoying: working with arrays.

Let's consider the following scenario: you have a Javascript array of strings that you need to convert into an array of numbers like so:

let numbers  = ["5", "8", "9", "11", "1", "6"]

Converting them to numbers should be simple enough. Using the map method, we can take in an array of our choice, transform it and return a new array. If you're like me, you'll probably rush into it and offer the following as a solution:

let numbers  = ["5", "8", "9", "11", "1", "6"];
let arrayOfNumbers = numbers.map(parseInt);

Only to run the code and find the following as your output:

console.log(arrayOfNumbers) 
// Array(6) [ 5, NaN, NaN, 4, 1, NaN ]

That's clearly not right. So, what's going on? Why doesn't it return an array of numbers as we'd expect? Allow me to introduce you to one of the oddities of Javascript: truthiness and falsiness.

Truthiness and falsiness in Javascript

A 'feature' that people moving over from other languages into Javascript may take some time getting used to is 'truthiness' and 'falsiness'.

Consider the following block of code:

let myString = "hello world"

if (myString){
  console.log("Hello World!")
} else {
  console.log("Goodbye Cruel World!")
}

//logs 'Hello World!'

Quoting MDN

In JavaScript, a truthy value is a value that is considered true when encountered in a Boolean context. All values are truthy unless they are defined as falsy (i.e., except for false, 0, 0n, "", null, undefined, and NaN).

In other words, everything is truthy except false, 0, null, undefined and NaN.

let zero = 0; //false, null, undefined, NaN
console.log(!zero) //true

The problem with this, of course is that:

let arr = ![];
let arr1 = ![1,2,3]
let obj = !{};
let obj1 = !{hello: "world"}

console.log(arr, arr1) // false, false
console.log(obj, obj1) // false, false

But that's a discussion for a whole other time.

Passing arguments in Javascript

Javascript is a functional language. The primary blocks of execution are functions. And functions can be defined as 'values that can be called'. Therefore, understanding how functions work is fundamental to the nature of Javascript.

Thanks to the power of higher order functions, we can call functions as the parameter of other functions.

let spottedVehicles = [
  {
    "number_plate": "CDY-1820",
    "kind": "motor_cycle",
    "speed": "120",
    "units": "kph"
  },
  {
    "number_plate": "DDE-8200",
    "kind": "car",
    "speed": "180",
    "units": "kph"
  },
  {
    "number_plate": "CDX-1740",
    "kind": "car",
    "speed": "60",
    "units": "kph"
  },
  {
    "number_plate": "CDX-0674",
    "kind": "motor-cycle",
    "speed": "40",
    "units": "kph"
  },
]

let isMotorCycle = obj=> obj.kind == "motor_cycle";

let getMotorCycles = vehicles => vehicles.filter(isMotorCycle);

let countMotorCycles = motorCycles => motorCycles.length;

let numberOfMotorCycles = countMotorCycles(getMotorCycles())

console.log(numberOfMotorCycles) //2

Javascript doesn't enforce arity. That is, you can call any function with as many or as little arguments as you like without an error being thrown. Therefore, the number of parameters expected by the function and those passed to the function can differ in two ways:

  • Too many arguments: Any extra arguments are ignored by the function
  • Too few arguments: Arguments not passed to the function are considered undefined.

Using map and parseInt together

The map function takes in another function and returns an array based on the transformation the argument function defines.

Say we wanted to double every element in the following array: [1,2,3,4,5]

let arr = [1,2,3,4,5];
doubledArr = arr.map(number=> number*2);
console.log(doubledArr) //[2,4,6,8,10]

Remember, however, that map passes three arguments. It's signature is arr.map(function callback(element, index, array){ })

image of sth...

On the other hand, the parseInt Javascript syntax signature is: parseInt(string, radix);. The third argument passed to it is ignored.

When we combine them:

let numbers  = ["5", "8", "9", "11", "1", "6"];
numbers.map(parseInt)

/*On the 1st iteration, parseInt will look like: */ parseInt("5", 0) //5
/*On the 2nd iteration, parseInt will look like: */ parseInt("8", 1) //NaN
/*On the 3rd iteration, parseInt will look like: */ parseInt("9", 2) //NaN
/*On the 4th iteration, parseInt will look like: */ parseInt("11", 3) //4
/*On the 5th iteration, parseInt will look like: */ parseInt("1", 4) //1
/*On the 6th iteration, parseInt will look like: */ parseInt("6", 5) //NaN

'Radix' is a fancy way of indicating the number of symbols a numbering system has. radix 2 only has two symbols, which are 0 and 1 (binary), and radix 5 has five symbols (0,1,2,3,4). Attempting to pass symbols outside of these bounds will result in NaN.

Note the use of the word 'symbol' rather than 'number.' The following is perfectly valid, for example: parseInt(333, 4) //63

The parseInt function receives the index parameter from the map function. This is passed as the second argument (the radix), so that: parseInt("5", 0) results in '5', but parseInt(8, 1) gives us NaN. The reason for this is one is a bit... complicated, so let's just skip over it.

The Solution

So, how do we resolve this quirky behaviour? Two ways:

Use Number

let numbers  = ["5", "8", "9", "11", "1", "6"];
numbers.map(Number)

This has the advantage of working with floating point numbers, too!

['8.1', '3.6e2', '8e300'].map(Number); // [8.1, 360, 8e+300]

In comparison, parseInt would result in

['8.1', '3.6e2', '8e300'].map(parseInt); // [8, 3, 8]

Use a concise arrow function

let numbers  = ["5", "8", "9", "11", "1", "6"];
numbers.map(str=> parseInt(str))

Copyright © 2020 The Kenyan Dev