Skip to main content
Ben Nadel at cf.Objective() 2009 (Minneapolis, MN) with: Mark Mandel
Ben Nadel at cf.Objective() 2009 (Minneapolis, MN) with: Mark Mandel ( @Neurotic )

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

55 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,377 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,377 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,377 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.
I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel