RadDevon

Why can't I use parseInt with map in Javascript?

I recently had a case where I wanted to break an IP address into the numerical values of its octets. I started with a string like this:

'192.168.1.1';

with the goal of having an array with each of the values like this:

[192, 168, 1, 1];

My first attempt was to split the string on the dots making an array. Then, I had an array of the octets as strings. To get an array of numbers, I decided to run the array’s map method passing in the parseInt function. Here’s what that might look like:

const ip = '192.168.1.1'; const ipArray = ip.split('.').map(parseInt);

The results were not what I expected.

[192, NaN, 1, 1];

What makes this more puzzling is that each octet worked except for the second which yielded NaN (not a number). Why does this happen? To understand this problem, we need a better understanding of both map and parseInt.

Intro to map()

Javascript arrays have a handy map method that generates a new array by running each value in the old array through a function (the callback passed into map). Here’s a quick example that doubles each value in an array of numbers:

const numbers = [1, 2, 3, 4]; const numbersDoubled = numbers.map((currentNumber) => currentNumber * 2); console.log(numbersDoubled); // [2, 4, 6, 8]

The callback function takes a parameter currentNumber and returns the value multiplied by 2. The map method of the array passes each value in turn to our doubling callback function and returns a new array of the results returned from the callback.

Intro to parseInt()

parseInt parses an integer from a string. This is good when you’re dealing with user input as you’ll usually get it as a string. You might want to use it as a number though. Here’s a quick example that doubles a user-entered number:

const userNumberAsString = prompt('Enter a number:'); const userNumber = parseInt(userNumberAsString); const numberDoubled = userNumber * 2; console.log(numbersDoubled); // Logs the number the user entered multiplied by 2

Fleshing Out Our Understanding

What we expected to happen when we ran const ipArray = ip.split('.').map(parseInt); is for Javascript to iterate over the array passing each of the octet strings to parseInt resulting in the string values being converted to numeric values. Let’s review the documentation for both map and parseInt to validate our assumptions about how these work.

map()

The map documentation gives us some information about how the callback is called that we might not know even if we frequently use it. (I frequently forget it myself even though I use map routinely.)

callback is invoked with three arguments: the value of the element, the index of the element, and the Array object being traversed.

I generally use map by passing in an anonymous function, and I typically don’t use the index or the array object. That makes it easy for me to forget they are being passed. Here, though, it could be the reason we’re getting odd results from parseInt. I believe parseInt takes a single argument: the string to be parsed into an integer, but I need to confirm that.

parseInt()

Once again, my understanding here is incomplete. From the parseInt documentation, we learn it actually takes two arguments. The second argument is the radix of the first argument:

An integer between 2 and 36 that represents the radix (the base in mathematical numeral systems) of the above mentioned string.

What’s Actually Happening

With our new understanding, we can dissect the problem. parseInt takes the radix as the second argument. map is passing three arguments, the second of which is the index of the current item. The index of the current octet in the array is being passed to parseInt although it expects the radix of the string being parsed instead.

Want more information about the radix? It’s the same as the base of another number system. Still lost? I’ve got a post on other number systems that will help.

Here are the parseInt calls map is making for our example IP address array:

parseInt('192', 0); // 192 parseInt('168', 1); // NaN parseInt('1', 2); / 1 parseInt('1', 3); / 1

The next question to help us understand is this: why does it work for all the octets except the second one?

The first octet works because the 0 radix argument is falsey. (Javascript evaluates 0 as false when it’s evaluated as a boolean.) The effect is the same as not passing a radix argument.

The second octet is the one that fails. We know from the documentation that Javascript expects a value between 2 and 36 for the radix. parseInt isn’t implemented to parse an integer with a radix value of 1, so it returns NaN.

The third and fourth octets both work just because their values happen to be '1'. '1' is represented the same regardless of the radix. If these had been different numbers, you would have seen more NaN values in the results.

How to Fix It

Now that we understand the problem, we can write a solution. Rather than passing the parseInt function to the array’s map method, we’ll pass an anonymous function that calls parseInt with exactly the arguments we want. Here’s our fixed array mapping:

const ip = '192.168.1.1'; const ipArray = ip.split('.').map(octet => parseInt(octet, 10));

We’re only using the first value passed to the map’s callback (because we don’t care about the index or the entire array). From the anonymous function, we’re returning the result of parseInt(octet, 10), with octet being the string of that octet of the IP. I specified the radix here because of something else I picked up from the parseInt documentation on MDN. If you read the entire description of the parseInt function’s radix parameter, you’ll find this:

Always specify this parameter to eliminate reader confusion and to guarantee predictable behavior. Different implementations produce different results when a radix is not specified, usually defaulting the value to 10.

If we don’t specify the radix of the integer we’re parsing, the results could be unpredictable. In practice, I haven’t found that to be the case, but better to be explicit than rely upon different implementations having the same default value for radix.

Lessons Learned

We learned map passes three arguments to the callback rather than just the current value. We learned parseInt takes a second argument. More importantly than both of those, though, we learned that, if the results of your code are not what you expect, make sure you reference the documentation for anything you didn’t write yourself (in our case, map and parseInt) to be sure that your assumptions about how it works are correct.

Making these kinds of assumptions generally makes writing code much faster, but it can lead to problems like the ones we experienced here. As long as you know how to pull back and rebuild your mental models, you’ll be able to fix your issues and move forward quickly.