IsValid Cannot Answer the Question of Is It Really An Integer


In the course of developing a good, secure, well functioning web application, you will inevitable spend quite a few lines of codes validating input so that you know it's meeting whatever business rules/logic may apply. ColdFusion has quite a few functions to help with validations, including the IsValid function, which let's you pass in a value and a type that it should be and get a true/false result. You can also do checks as to whether a value is within a certain range or run it against a regular expression pattern.

On the whole, IsValid is a darn useful function, with its 25 available type matches, including email, creditcard, telephone, zipcodes, dates, and of course basic variable types like integer, numeric, string, and boolean. Unfortunately, it has one flaw that may be a “gotcha” for users who don't dig through the validation. Namely, when it comes to some of the variable types, ColdFusion is really checking it against the SQL standard, not the general meaning.

For example, the one that brought this to my attention was my trying to do a quick check to make sure a list of values is a list of numeric values. I thought I was clever by just checking to see if it was an integer once the delimiter was removed. Now, mathematically and in common terms, an integer is a number with no decimal or fractional component, i.e. a “whole number.” So, using IsValid, you would have a good reason expect all of these to return true:

<cfoutput>
#IsValid(“Integer”, 1)#
#IsValid(“Integer”, 1234)#
#IsValid(“Integer”, 12345678)#
#IsValid(“Integer”, 1234567890)#
#IsValid(“Integer”, 11234567890987654321)#
</cfoutput>

And these to all return false:

<cfoutput>
#IsValid(“Integer”, '1.0')#
#IsValid(“Integer”, '12.34')#
#IsValid(“Integer”, '$2345.78')#
#IsValid(“Integer”, '$1,23,45,67')#
#IsValid(“Integer”, '$1,234,567')#
#IsValid(“Integer”, 'A123456')#
</cfoutput>

In reality what you get from the two sets is:

Should all be integers:

IsValid(“Integer”, 1) = YES
IsValid(“Integer”, 1234) = YES
IsValid(“Integer”, 12345678) = YES
IsValid(“Integer”, 1234567890) = YES
IsValid(“Integer”, 11234567890987654321) = NO


Should NOT be integers:

IsValid(“Integer”, '1.0') = NO
IsValid(“Integer”, '12.34') = NO
IsValid(“Integer”, '$2345.78') = NO
IsValid(“Integer”, '$1,23,45,67') = YES
IsValid(“Integer”, '$1,234,567') = YES
IsValid(“Integer”, 'A123456') = NO

WTF? Well, the reason for it is ColdFusion's IsValid for the integer type does NOT validate that it is an integer in normal terms, rather it is validating that it meets the SQL and some programming standards of being an “long integer”. So any number that isn't in the range of ?2,147,483,648 to +2,147,483,647 will fail. Interesting enough, it isn't even using the Java range, which is ?9,223,372,036,854,775,808 to +9,223,372,036,854,775,807; instead the range it uses harkens back to its C++ based days (presumably for backwards compatibility). Further, when doing it's IsValid check for Integers, it ignores commas – it doesn't even check if they are in a “proper” place!

So what does this mean for you? Well, if you want to know if something is an integer in the mathematical sense, and do not care if it is within an SQL integer range, you need to use a different validation method. The question is, what alternatives are there. I've seen a few suggestions floating around the net on getting around this, which I'm compiling here and showing the results of each:

Use IsNumeric (or IsValid with the Numeric type)

Will fail anything that isn't numerals and a decimal point, which will work for most needs. You could combine this in conjunction with looking for decimal separately if you want to be sure it is an actual integer versus a decimal,

#IsNumeric(11234567890987654321)# YES
#IsNumeric('$1,23,45,67')# NO
#IsNumeric('12.34')# YES
#IsNumeric('$2345.78')# NO
#IsNumeric('234,578')# NO
#IsNumeric('A123456')# NO
#IsValid(“Numeric”, 11234567890987654321)# YES
#IsValid(“Numeric”, '$1,23,45,67')# NO
#IsValid(“Numeric”, '12.34')# YES
#IsValid(“Numeric”, '$2345.78')# NO
#IsValid(“Numeric”, '234,578')# NO
#IsValid(“Numeric”, 'A123456')# NO

Using Round to do a comparison

Using this method, you have to wrap your check in a CFTRY/CFCATCH as Round will not accept non-numbers at all. You can see below the two that failed through the catch versus the equivalent failing.

#ROUND(11234567890987654321) EQ 11234567890987654321# NO
#ROUND('A123456') EQ 'A123456'# Not Valid (cfcatch)
#ROUND('$1,234,567') EQ '$1,234,567'# Not Valid (cfcatch)

But you probably also noticed that the first one failed too. If you output the results of #ROUND(11234567890987654321)#, you'll see why:

9.22337203685E+018

Yep, with larger numbers, Round returns them in scientific notation.

Using some Regular Expression Checks (courtesy of the incomparable Ray Camden 🙂 )

On the whole, these seem to work pretty decently. However, if your number has a leading zero, it will also come back as invalid (which technically it is). If you did need to have long numbers with leading zeros, if you change the [1-9] to [0-9], it should then work.

#IsValid(“RegEx”, 11234567890987654321, “^-{0,1}[1-9]+[\d]*”)# YES
#IsValid(“RegEx”, 11234567890987654321, “^-{0,1}[1-9]+[\d]*”)# YES
#IsValid(“RegEx”, 01345, “^-{0,1}[1-9]+[\d]*”)# NO
#IsValid(“RegEx”, '1.0', “^-{0,1}[1-9]+[\d]*”)# NO
#IsValid(“RegEx”, '12.34', “^-{0,1}[1-9]+[\d]*”)# NO
#IsValid(“RegEx”, $2345.78', “^-{0,1}[1-9]+[\d]*”)# NO
#IsValid(“RegEx”, A123456', “^-{0,1}[1-9]+[\d]*”)# NO

Going down to the Java Level

This method requires wrapping the checks in a try/catch as it outright fails if it isn't a valid integer; and Java's parseInt doesn't actually let you go up to it's own integer limit, rather it enforces the more standard range.

#createObject(“java”,”java.lang.Integer”).parseInt(“987654321987)# Not Valid (cfcatch)
#createObject(“java”,”java.lang.Integer”).parseInt(“1.0”)# Not Valid (cfcatch)
#createObject(“java”,”java.lang.Integer”).parseInt(“A123456”)# Not Valid (cfcatch)
#createObject(“java”,”java.lang.Integer”).parseInt(“$2345.78”)# Not Valid (cfcatch)
#createObject(“java”,”java.lang.Integer”).parseInt(“1234567890”)# 1234567890

So for my needs, it looks like the regular expression is the best option, in conjunction with stripping out the delimiter, to ensure that my list of stuff is really a list of integers before I shove it to the database or try to do other stuff to it.