JavaScript Basics: Validate Numbers from Text
A text field containing 12 still holds text. Before doing arithmetic, decide which inputs are acceptable, convert them, and check the result. This tutorial builds a finite-number reader and a stricter quantity reader.
All examples use built-in features and were tested on Linux with Node.js v24.19.0. Each file includes assertions; a failed assertion stops the program.
Prepare a scratch directory
Run this in Bash and keep the same terminal open:
You need Bash, a text editor, the mktemp, rm, and rmdir utilities, and Node.js on your PATH. Save source files as UTF-8; their .mjs extension selects ES modules.
start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1Save the following files in this directory using your editor. No packages are required.
1. Convert text and require a finite number
Save this as finite-number.mjs:
import assert from 'node:assert/strict';
function readFiniteNumber(text) {
if (typeof text !== 'string' || text.trim() === '') {
throw new TypeError('Expected non-empty text');
}
const value = Number(text);
if (!Number.isFinite(value)) {
throw new RangeError('Expected a finite number');
}
return value;
}
assert.equal(readFiniteNumber(' 12.5 '), 12.5);
assert.equal(readFiniteNumber('0'), 0);
assert.equal(readFiniteNumber('1e3'), 1000);
assert.equal(readFiniteNumber('0x10'), 16);
assert.equal(Number(''), 0);
assert.equal(Number(' '), 0);
assert.equal(Number.isFinite('12'), false);
for (const input of ['', ' ', null, 12]) {
assert.throws(() => readFiniteNumber(input), TypeError);
}
for (const input of ['12px', 'NaN', 'Infinity', '-Infinity', '1e309']) {
assert.throws(() => readFiniteNumber(input), RangeError);
}
console.log(`Decimal: ${readFiniteNumber(' 12.5 ')}`);
console.log(`Exponent: ${readFiniteNumber('1e3')}`);
console.log(`Hexadecimal: ${readFiniteNumber('0x10')}`);
console.log('Invalid inputs rejected');Run it:
node finite-number.mjsOutput:
Decimal: 12.5
Exponent: 1000
Hexadecimal: 16
Invalid inputs rejectedNumber(text) converts the whole string. Invalid numeric text produces NaN. Empty or whitespace-only strings become zero, which is why the function checks for blank input first. The explicit type check also keeps values such as null out of this text-only interface.
Number.isFinite() accepts only finite numbers. It rejects NaN, either infinity, and strings without converting them. Convert first, then check.
This reader deliberately accepts exponent notation and hexadecimal text. If your input format only allows decimal digits, enforce that format too, as the next example does.
2. Validate a whole-number quantity
Save this as quantity.mjs:
import assert from 'node:assert/strict';
function readQuantity(text) {
if (typeof text !== 'string') {
throw new TypeError('Expected text');
}
const trimmed = text.trim();
// One or more ASCII digits; no sign, decimal point, or exponent.
if (!/^[0-9]+$/.test(trimmed)) {
throw new RangeError('Expected decimal digits');
}
const value = Number(trimmed);
if (!Number.isSafeInteger(value)) {
throw new RangeError('Quantity is outside the safe integer range');
}
return value;
}
assert.equal(readQuantity(' 007 '), 7);
assert.equal(readQuantity('0'), 0);
assert.equal(readQuantity('9007199254740991'), Number.MAX_SAFE_INTEGER);
assert.equal(Number.isInteger(2.5), false);
assert.equal(Number.isInteger(2 ** 53), true);
assert.equal(Number.isSafeInteger(2 ** 53), false);
assert.throws(() => readQuantity(null), TypeError);
for (const input of ['', '-1', '+1', '2.5', '1e3', '0x10', '12px',
'9007199254740992', '9007199254740993']) {
assert.throws(() => readQuantity(input), RangeError);
}
console.log(`Quantity: ${readQuantity(' 007 ')}`);
console.log(`Largest safe quantity: ${readQuantity('9007199254740991')}`);
console.log(`2 ** 53 is an integer: ${Number.isInteger(2 ** 53)}`);
console.log(`2 ** 53 is safe: ${Number.isSafeInteger(2 ** 53)}`);Run it:
node quantity.mjsOutput:
Quantity: 7
Largest safe quantity: 9007199254740991
2 ** 53 is an integer: true
2 ** 53 is safe: falseNumber.isInteger() checks whether the stored number is integral. It cannot recover precision already lost during conversion.
Number.isSafeInteger() also requires the number to be between -(2 ** 53 - 1) and 2 ** 53 - 1, inclusive. Outside this range, different integers can become the same Number. Use safe-integer checks for counts that must remain exact, and consider BigInt for larger integers.
The regular expression checks the text format before conversion. This quantity rule allows zero and leading zeros, but rejects signs and decimal points. For a real order form, add its business limit, such as a maximum of 100 items, after these checks. Identifiers with meaningful leading zeros should generally stay strings.
3. Know when parseInt stops
Save this as parse-int-caveat.mjs:
import assert from 'node:assert/strict';
assert.equal(parseInt('12px', 10), 12);
assert.equal(parseInt('3.9', 10), 3);
assert.equal(parseInt('1e3', 10), 1);
assert.equal(Number.isNaN(Number('12px')), true);
assert.equal(Number('1e3'), 1000);
console.log(`parseInt('12px', 10): ${parseInt('12px', 10)}`);
console.log(`parseInt('3.9', 10): ${parseInt('3.9', 10)}`);
console.log(`parseInt('1e3', 10): ${parseInt('1e3', 10)}`);
console.log(`Number('1e3'): ${Number('1e3')}`);Run it:
node parse-int-caveat.mjsOutput:
parseInt('12px', 10): 12
parseInt('3.9', 10): 3
parseInt('1e3', 10): 1
Number('1e3'): 1000parseInt() reads an integer prefix and stops when it meets a character that cannot belong to that integer. The explicit radix 10 selects decimal parsing. This behavior is useful for intentional prefix extraction, but it does not validate that an entire input is an integer. For a quantity, use the text-format and safe-integer checks above.
Clean up
Run this in the same terminal to remove the three tutorial files and their empty scratch directory:
rm -- finite-number.mjs quantity.mjs parse-int-caveat.mjs
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dirAcknowledgements
This post was created with assistance from AI.