Skip to content

Instantly share code, notes, and snippets.

@hexadeciman
Last active February 17, 2025 10:02
Show Gist options
  • Save hexadeciman/1ef3007c65c1bb2667d9616705947ae3 to your computer and use it in GitHub Desktop.
Save hexadeciman/1ef3007c65c1bb2667d9616705947ae3 to your computer and use it in GitHub Desktop.
A robust utility function to download data as a JSON file, supporting large datasets, circular reference handling, optional timestamp inclusion, and automatic splitting into smaller chunks for oversized data.
/**
* Configuration options for JSON download
*/
interface DownloadJsonOptions {
filename?: string;
indent?: number;
includeTimestamp?: boolean;
prettify?: boolean;
chunkSize?: number; // Size in MB for splitting large data
}
/**
* Enhanced utility function to download data as a JSON file
* Handles large datasets and circular references
* @param data - The data to be downloaded
* @param options - Configuration options
*/
export function downloadAsJson(data: unknown, options: DownloadJsonOptions = {}): void {
const {
filename = "data",
indent = 2,
includeTimestamp = false,
prettify = true,
chunkSize = 700, // Default 50MB chunks
} = options;
try {
// Prepare the data
let finalData = data;
if (includeTimestamp) {
finalData = {
data,
timestamp: new Date().toISOString(),
metadata: {
exportedAt: new Date().toISOString(),
browser: navigator.userAgent,
},
};
}
// Handle circular references
const seen = new WeakSet();
const sanitizedData = JSON.stringify(
finalData,
(key, value) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) {
return "[Circular Reference]";
}
seen.add(value);
}
return value;
},
prettify ? indent : undefined,
);
// Check size of the string
const bytes = new TextEncoder().encode(sanitizedData).length;
const maxBytes = chunkSize * 1024 * 1024; // Convert MB to bytes
if (bytes > maxBytes) {
// For large datasets, split into multiple files
handleLargeDownload(finalData, options);
return;
}
// Create blob and trigger download
const blob = new Blob([sanitizedData], {
type: "application/json",
});
downloadBlob(blob, filename);
} catch (error) {
// console.error("Error downloading JSON file:", error);
throw new Error(`Failed to download JSON file: ${error instanceof Error ? error.message : "Unknown error"}`);
}
}
/**
* Helper function to handle large datasets by splitting them into chunks
*/
function handleLargeDownload(data: unknown, options: DownloadJsonOptions): void {
if (Array.isArray(data)) {
// If data is an array, split it into chunks
const chunkSize = 1000; // Number of items per chunk
for (let i = 0; i < data.length; i += chunkSize) {
const chunk = data.slice(i, i + chunkSize);
const chunkFilename = `${options.filename || "data"}_part${Math.floor(i / chunkSize) + 1}`;
const sanitizedChunk = JSON.stringify(chunk, null, options.prettify ? options.indent || 2 : undefined);
const blob = new Blob([sanitizedChunk], { type: "application/json" });
downloadBlob(blob, chunkFilename);
}
} else if (typeof data === "object" && data !== null) {
// If data is an object, split by keys
const keys = Object.keys(data);
const chunkSize = Math.ceil(keys.length / 4); // Split into 4 parts
for (let i = 0; i < keys.length; i += chunkSize) {
const chunkKeys = keys.slice(i, i + chunkSize);
const chunk = chunkKeys.reduce(
(acc, key) => {
acc[key] = data[key as keyof typeof data];
return acc;
},
{} as Record<string, unknown>,
);
const chunkFilename = `${options.filename || "data"}_part${Math.floor(i / chunkSize) + 1}`;
const sanitizedChunk = JSON.stringify(chunk, null, options.prettify ? options.indent || 2 : undefined);
const blob = new Blob([sanitizedChunk], { type: "application/json" });
downloadBlob(blob, chunkFilename);
}
}
}
/**
* Helper function to handle the actual download
*/
function downloadBlob(blob: Blob, filename: string): void {
const fullFilename = filename.endsWith(".json") ? filename : `${filename}.json`;
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = fullFilename;
link.style.display = "none";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
setTimeout(() => {
URL.revokeObjectURL(url);
}, 100);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment