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:
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
Intro to map()
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
parseInt resulting in the string values being converted to numeric values. Let’s review the documentation for both
parseInt to validate our assumptions about how these work.
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
callbackis 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.
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
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
false when it’s evaluated as a boolean.) The effect is the same as not passing a radix argument.
36 for the radix.
parseInt isn’t implemented to parse an integer with a radix value of
1, so it returns
The third and fourth octets both work just because their values happen to be
'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
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
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.
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,
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.