Skip to content

JSON Project Import And Export

Export JSON, edit a task, then import the exported file to restore the snapshot. Invalid JSON reports an error without replacing the project.

Source code
import { controlAttributes, createControl } from '@revolist/ui';
import { defineCustomElements } from '@revolist/revogrid/loader';
defineCustomElements();
import {
defineGanttToolbar,
GanttPlugin,
GanttCalendarEditorPlugin,
type ProjectSnapshot,
} from '@revolist/gantt';
import { ExportExcelPlugin, RowSelectPlugin } from '@revolist/revogrid-pro';
import { currentTheme } from '../composables/useRandomData';
import {
SHOWCASE_ASSIGNMENTS,
SHOWCASE_BASELINES,
SHOWCASE_COLUMNS_WITH_ROW_SELECT,
SHOWCASE_DEPENDENCIES,
SHOWCASE_RESOURCES,
SHOWCASE_TOOLBAR_COLUMNS,
STANDARD_CALENDAR,
} from './shared/gantt-project-data';
import {
createProductionGanttConfig,
createProductionTasks,
createProductionPersistence,
productionError,
productionSourceRows,
} from './GanttProductionPatternsShared';
import './gantt-production-patterns.css';
export function load(parentSelector: string) {
const parent = document.querySelector(parentSelector);
if (!parent) return () => {};
const root = document.createElement('section');
const controls = document.createElement('div');
const status = document.createElement('p');
const toolbar = document.createElement('div');
const grid = document.createElement('revo-grid');
const persistence = createProductionPersistence();
let readOnly = false;
let localized = false;
let large = false;
let project: ProjectSnapshot | null = null;
const applyConfig = () => {
grid.gantt = createProductionGanttConfig(readOnly, localized, project);
};
const applyProject = () => {
applyConfig();
grid.ganttDependencies =
project?.dependencies ?? (large ? [] : SHOWCASE_DEPENDENCIES);
grid.ganttCalendars = project?.calendars ?? [STANDARD_CALENDAR];
grid.ganttResources = project?.resources ?? SHOWCASE_RESOURCES;
grid.ganttAssignments =
project?.assignments ?? (large ? [] : SHOWCASE_ASSIGNMENTS);
grid.ganttBaselines =
project?.baselines ?? (large ? [] : SHOWCASE_BASELINES);
grid.source = project
? productionSourceRows(project)
: createProductionTasks(large);
};
const toggle = (label: string, update: (value: boolean) => void) => {
const element = document.createElement('label');
const input = createControl(document, 'input', { type: 'checkbox' });
input.type = 'checkbox';
input.addEventListener('change', () => update(input.checked));
element.append(input, document.createTextNode(label));
return { element, input };
};
const run = async (action: () => unknown | Promise<unknown>) => {
try {
await action();
} catch (error) {
status.textContent = productionError(error);
}
};
const button = (label: string, action: () => unknown | Promise<unknown>) => {
const element = createControl(document, 'button', { type: 'button' });
element.type = 'button';
element.textContent = label;
element.className = controlAttributes('button', { class: 'rv-btn' }).class;
element.addEventListener('click', () => {
void run(action);
});
return element;
};
root.className = 'gantt-production-demo';
controls.className = 'gantt-production-controls';
status.className = 'gantt-production-status';
status.setAttribute('role', 'status');
toolbar.className = 'gantt-production-toolbar';
grid.className = 'gantt-production-grid';
grid.theme = currentTheme().isDark() ? 'darkCompact' : 'compact';
grid.hideAttribution = true;
grid.plugins = [
GanttPlugin,
GanttCalendarEditorPlugin,
ExportExcelPlugin,
RowSelectPlugin,
];
grid.rowSelect = { rowOrder: true };
grid.columns = [...SHOWCASE_COLUMNS_WITH_ROW_SELECT];
const largeToggle = toggle('750 tasks', (value) => {
large = value;
project = null;
applyProject();
});
const restore = button('Restore', async () => {
project = await persistence.load();
large = false;
largeToggle.input.checked = false;
applyProject();
status.textContent =
'Restored the saved project, including tasks and project collections.';
});
restore.disabled = true;
const fileInput = createControl(document, 'input', { type: 'file' });
fileInput.type = 'file';
fileInput.hidden = true;
fileInput.accept = '.json,application/json';
fileInput.setAttribute('aria-label', 'Import JSON file');
fileInput.addEventListener('change', async () => {
const file = fileInput.files?.[0];
if (file)
await run(async () => {
project = await persistence.importFile(file);
large = false;
largeToggle.input.checked = false;
applyProject();
status.textContent = `Imported ${project.tasks.length} tasks from ${file.name}.`;
});
fileInput.value = '';
});
const options = document.createElement('div');
options.className = 'gantt-production-options';
const actions = document.createElement('div');
actions.className = 'gantt-production-actions';
options.append(
toggle('Read only', (value) => {
readOnly = value;
applyConfig();
}).element,
toggle('Español dates', (value) => {
localized = value;
applyConfig();
}).element,
largeToggle.element,
);
actions.append(
fileInput,
button('Import JSON', () => fileInput.click()),
button('JSON', () => persistence.exportFile(grid)),
button('Save', async () => {
await persistence.save(grid);
restore.disabled = false;
status.textContent =
'Saved the current edited project in browser memory.';
}),
restore,
button('Print', () => persistence.print(grid)),
);
controls.append(options, actions);
root.append(controls, status, toolbar, grid);
parent.appendChild(root);
applyProject();
defineGanttToolbar(toolbar, {
grid,
columns: SHOWCASE_TOOLBAR_COLUMNS,
controls: { baseline: true, calendars: true },
});
return () => {
toolbar.textContent = '';
grid.remove();
root.remove();
};
}

JSON project import/export provides a framework-neutral way to move complete Gantt project snapshots between files, APIs, tests, undo buffers, and server-side workflows. The helpers operate on ProjectSnapshot data only. They do not depend on the grid, DOM, or framework wrappers.

Import snapshot data helpers from @revolist/gantt and the file-picker recipe from your copied examples:

The application recipes below live in the examples source browser. Copy their recipes/ files beside your code before using the relative imports; they are maintained examples rather than published Gantt exports.

import {
cloneProjectSnapshot,
exportProjectSnapshot,
parseProjectSnapshotJson,
} from '@revolist/gantt';
import { createJsonSnapshotRecipe } from './recipes/persistence-examples';

Use exportProjectSnapshot() to serialize the current project snapshot.

const json = exportProjectSnapshot(project, { space: 2 });

The optional space value is passed to JSON.stringify, so you can use it for readable file exports.

Use parseProjectSnapshotJson() to parse JSON into a typed result instead of throwing for common invalid payloads.

const parsed = parseProjectSnapshotJson(json);
if (!parsed.ok) {
showImportError(parsed.error);
return;
}
applyProject(parsed.project);

The parser checks the top-level project shape:

  • Required string fields: id, name, version, currency, timeZone, primaryCalendarId, and updatedAt.
  • Required array fields: tasks, dependencies, resources, assignments, calendars, and baselines.

It intentionally performs lightweight validation. Run scheduler validation or your application-specific validation after import when you need deeper checks for dates, task references, dependency cycles, resource assignments, or business rules.

Use cloneProjectSnapshot() when you need a JSON-safe deep copy before staging edits.

const draft = cloneProjectSnapshot(project);
draft.name = 'Scenario copy';

The clone is detached from nested task, dependency, resource, assignment, calendar, and baseline arrays.

For host applications that own file pickers and save dialogs, use createJsonSnapshotRecipe() with injected text readers and writers.

const recipe = createJsonSnapshotRecipe({
readText: () => selectedFile.text(),
writeText: (fileName, contents) => downloadTextFile(fileName, contents),
fileName: (project) => `${project.id}-snapshot.json`,
});
const imported = await recipe.load();
await recipe.save(project, 2);

The recipe throws an Error when load/save hooks are missing or the imported JSON fails parsing. Use the lower-level parser directly when you prefer result objects over exceptions.

The exported JSON is the project snapshot, not rendered grid state. It includes project metadata and entity arrays:

  • Tasks
  • Dependencies
  • Resources
  • Assignments
  • Calendars
  • Baselines

It does not include projected Gantt row fields, rendered timeline geometry, current scroll position, expanded row state, active selection, filters, or toolbar state.

The REST, GraphQL, Supabase, and PostgreSQL examples build on the same JSON snapshot helpers. Use those adapters when the snapshot should be loaded from or saved to an application backend.

See Integrations And Frameworks for adapter examples.