Skip to main content
Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.

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

By Ben Nadel on
Tags: ColdFusion

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.



Reader 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

Reply to this Comment

@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 :)

Reply to this Comment

@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.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Blog
Live in the Now
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.