Skip to content

Instantly share code, notes, and snippets.

@z4nr34l
Created May 2, 2025 10:01
Show Gist options
  • Save z4nr34l/4b12c82841e8ab76aca545f77ea9418e to your computer and use it in GitHub Desktop.
Save z4nr34l/4b12c82841e8ab76aca545f77ea9418e to your computer and use it in GitHub Desktop.
TypeScript smart sorting primitive
/**
* Smart sorting utility that detects data types and applies appropriate sorting methods
* - Strings are compared case-insensitively
* - Dates are compared as Date objects
* - Numbers are compared numerically
* - Booleans are compared as booleans (false first, true second)
*/
/**
* Detects the data type of a value
*/
export function detectDataType(
value: unknown,
): "string" | "number" | "date" | "boolean" | "other" {
if (value === null || value === undefined) {
return "other";
}
if (typeof value === "boolean") {
return "boolean";
}
if (typeof value === "number" && !isNaN(value)) {
return "number";
}
if (typeof value === "string") {
// Check if it's a valid date string
const date = new Date(value);
// Ensure it's a valid date format (not "Invalid Date") and contains dashes or slashes suggesting date format
if (
!isNaN(date.getTime()) &&
(value.includes("-") || value.includes("/") || value.includes("T"))
) {
return "date";
}
// Check if it's a numeric string
if (/^-?\d+(\.\d+)?$/.test(value.trim())) {
return "number";
}
return "string";
}
return "other";
}
/**
* Compares two values based on their detected data types
*/
export function smartCompare(
a: unknown,
b: unknown,
order: "asc" | "desc" = "asc",
): number {
// Determine if values are null/undefined for special handling
const aIsNullish = a === null || a === undefined;
const bIsNullish = b === null || b === undefined;
// Handle null/undefined values (always push them to the end)
if (aIsNullish && bIsNullish) return 0;
if (aIsNullish) return order === "asc" ? 1 : -1;
if (bIsNullish) return order === "asc" ? -1 : 1;
// Detect data types
const typeA = detectDataType(a);
const typeB = detectDataType(b);
// If types differ, sort by type hierarchy (string < number < date < boolean < other)
if (typeA !== typeB) {
const typeHierarchy = {
string: 1,
number: 2,
date: 3,
boolean: 4,
other: 5,
};
const result = typeHierarchy[typeA] - typeHierarchy[typeB];
return order === "asc" ? result : -result;
}
// Compare values based on their common type
let result = 0;
switch (typeA) {
case "string":
result = String(a).localeCompare(String(b), undefined, {
sensitivity: "base",
});
break;
case "number":
// Convert to numbers in case they are numeric strings
result = Number(a) - Number(b);
break;
case "date":
result =
new Date(a as string).getTime() - new Date(b as string).getTime();
break;
case "boolean":
result = a === b ? 0 : a ? 1 : -1;
break;
default:
// For other types, convert to string and compare
result = String(a).localeCompare(String(b));
}
return order === "asc" ? result : -result;
}
/**
* Smart sort function that can be used to sort arrays of objects
* @param data The array of objects to sort
* @param column The column/key to sort by
* @param order The sort order ('asc' or 'desc')
* @returns A new sorted array
*/
export function smartSort<T>(
data: readonly T[],
column: keyof T | string,
order: "asc" | "desc" = "asc",
): T[] {
return [...data].sort((a, b) => {
// Handle cases where the column might not exist on some items
const aValue =
a === null || a === undefined
? undefined
: typeof a === "object"
? (a as any)[column]
: undefined;
const bValue =
b === null || b === undefined
? undefined
: typeof b === "object"
? (b as any)[column]
: undefined;
return smartCompare(aValue, bValue, order);
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment