Skip to main content
Ben Nadel at cf.Objective() 2009 (Minneapolis, MN) with: Kurt Wiersma
Ben Nadel at cf.Objective() 2009 (Minneapolis, MN) with: Kurt Wiersma

Array.Sort() Operator Must Return INT-Sized Result In Lucee CFML 5.3.4.80

By on
Tags:

A few weeks ago, I wrote about using Subtraction to power the Array Sort operator in Lucee CFML. This has become my go-to approach for sorting arrays. However, yesterday, I ran into an edge-case in which Subtraction was throwing an error. It turns out, the number returned from the array.sort() operator has to fit into a Java int in Lucee CFML 5.3.4.80.

At InVision, when generating API responses, we usually convert Date/Time objects into UTC milliseconds so that the client-side / JavaScript code can create localized Date objects with a simple constructor call:

new Date( utcMilliseconds )

In my case, I had a list of Projects that needed to be sorted by date. And, since I had already converted the DateTime value into UTC milliseconds (for use in an API response), I went about trying to sort the projects using Subtraction:

<cfscript>

	// In our API responses, we return Date/Time stamps as UTC Milliseconds so that the
	// client-side code can simply do "new Date( utcMilliseconds )" and create a Date
	// object in the user's local timezone.
	projects = [
		{
			id: 1,
			name: "Really old project",
			createdAt: createDate( 2015, 12, 15 ).getTime() // 1450155600000
		},
		{
			id: 500,
			name: "Recent project",
			createdAt: createDate( 2019, 10, 30 ).getTime() // 1572408000000
		},
		{
			id: 1000,
			name: "Current project",
			createdAt: createDate( 2020, 02, 26 ).getTime() // 1582693200000
		}
	];

	projects.sort(
		( a, b ) => {

			// Sort the projects by CREATED DATE DESC (with most recent dates at the
			// top of the resultant array).
			// --
			// Ex: a: 2020, b: 2015
			// Result: ( (b)2015 - (a)2020 ) => -5 => (a) is sorted first.
			return( b.createdAt - a.createdAt );

		}
	);

	dump( projects );

</cfscript>

When I run this code, however, I was getting the following ColdFusion error:

Lucee sort error: invalid call of the function ArraySort, second Argument (function) is invalid, return value of the function [lambda_6i] cannot be casted to an integer.

invalid call of the function ArraySort, second Argument (function) is invalid, return value of the function [lambda_6i] cannot be casted to an integer.

Can't cast Object type [Number] to a value of type [integer]

arraysort(array:object, sorttype_or_closure:object, [sort_order:string, [locale_sensitive:boolean]]):boolean

The problem, as best I can tell, is that when I subtract one UTC millisecond value from another, I end up with a value that doesn't fit into a regular Java int. So, for example, one of the operator calls in the above code will result in the falling maths:

return( 1572408000000 - 1450155600000 )

... which gives us the value (with commas), 122,252,400,000. A signed Java int can only hold +/- (with commas) 2,147,483,647. As such, our mathematical result is too large by many billions.

To fix this issue, I am simply reverting back to a more deterministic set of operator responses:

<cfscript>
	
	// .....

	projects.sort(
		( a, b ) => {

			if ( a.createdAt == b.createdAt ) {

				return( 0 );

			}

			// Sort the projects by CREATED DATE DESC (with most recent dates at the
			// top of the resultant array).
			return( ( b.createdAt < a.createdAt ) ? -1 : 1 );

		}
	);

	// .....

</cfscript>

This requires a bit more code; but, reduces the set of possible return values to -1, 0, and 1, which we know will always fit into the underlying Java int. And, if we re-run the ColdFusion code with this updated sort operator, we get the following output:

Array with UTC milliseconds sort in descending order.

I still like the idea of using Subtraction to power the array.sort() operator in Lucee CFML; however, it's good to know that this approach comes with some caveats. Namely that the return value must fit into a Java int. I imagine that I'll only ever run into this issue when sorting arrays based on UTC milliseconds, since I'm not sure how else I would be dealing with such large numbers. In those cases, I can just fallback to using more traditional logic.

Want to use code from this post? Check out the license.

Reader Comments

54 Comments

I found a bug in Lucee after playing with your example, I would like to be able to label anonymous functions like in javascript, coz that [lambda_6i] is a bit confusing.

I tried defining a name for the component, but Lucee barfs, ACF is fine

function ( a, b ) name="comparator", throws name cannot be defined twice
https://luceeserver.atlassian.net/browse/LDEV-2792

15,260 Comments

@Zac,

Oh, that's interesting! It would never have even occurred to me that you could try to name a function using the meta-data after the function signature. But, I guess can we do that kind of thing with localMode and other constructs, so it makes sense. Good find :)

15,260 Comments

@Brad,

Ah, :face-palm: -- I am so used to using .sort() in JavaScript, it didn't occur to me that the it might be documented differently in ColdFusion / Lucee. I am sure that I read the docs, but I probably glossed right over it. And JavaScript is explicitly documented to be loosey-goosey with the values, ex from Mozilla Developer Network:

If compareFunction(a, b) returns less than 0, sort a to an index lower than b (i.e. a comes first)....

etc.

Thanks for pointing out that Lucee has more explicitly documented constraints. Though, as you point out, it actually does work to some degree.

15,260 Comments

@Adam,

Oh chickens! I don't think I've ever even seen the sgn function! Ha ha, no doubt this has been there since like ACF 7 or something and I just never saw it (or, if I did, wouldn't have know what it was useful for).

Post A Comment — I'd Love To Hear From You!

Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.