Skip to content

Persistence and imports ​

JSON persistence lives in vyuh_diagram_codec_json; SVG export lives in vyuh_diagram_codec_svg. Shared adapter contracts and output values belong to vyuh_diagram_types. Headless controllers depend on neither codec implementation.

A document format is a DiagramDocumentFormat<T>: a function that builds a DiagramDocumentCodec<T> over the installed extensions' model codecs. The JSON format is DiagramJsonCodec.new, from vyuh_diagram_codec_json.dart. Pass it to controller.save(...), controller.edits.load(...), or DiagramController.open(...); the diagram supplies its registry's codecs, so the content and text attributes each extension owns persist without wiring. For another format, pass its own format function the same way.

Headless SVG export requires installing SvgExtension() from package:vyuh_diagram_codec_svg/vyuh_diagram_codec_svg.dart in the configuration's extensions; vyuh_diagram_flutter does not re-export it. JSON preserves authored content for reopening; SVG exports a resolved scene and does not provide a lossless document round trip.

Save with controller.save(DiagramJsonCodec.new) and restore with controller.edits.load(json, format: DiagramJsonCodec.new) or DiagramController.open(json, format: DiagramJsonCodec.new). Install the same extensions when restoring, so their shapes, content and text attributes decode. The JSON format page specifies the document.

Connectors may carry an optional bundleKey, an explicit key for collapsed-frame connection presentation. The key does not merge or replace the underlying connectors. A frame's isCollapsed state is persisted while its expanded geometry, descendants, and original connector bindings are retained. Paths from explicit elbow rerouting, editable path nodes, multi-stop linear/radial fills and point-anchor bindings are also persisted. Point anchors store normalized frame coordinates, so they remain attached through owner transforms.

dart
final json = controller.save(DiagramJsonCodec.new);

save encodes controller.document, which includes that controller's own in-progress preview. Save outside an interaction, or encode the committed content every controller shares:

dart
final json = DiagramJsonCodec(controller.diagram.registry.codecs)
    .encode(controller.diagram.document);

Camera state, options such as the grid, rules and runtime path animations are controller or diagram state, not saved document content.

On this page ​

CategoryWhat you can do
Load a documentOpen JSON as a new controller, or load it into a live one.
Handle rejectionKeep the existing document when an import fails.
Format versionUnderstand which persisted version is read.
JSON formatRead the document keys and extension data.

Open or load a document ​

To open saved JSON as a new controller in Flutter or plain Dart, pass the format and the extensions that own the document's content:

dart
final controller = DiagramController.open(
  json,
  format: DiagramJsonCodec.new,
  configuration: DiagramConfiguration(
    extensions: [EditorExtension(), TableFlutterExtension()],
  ),
);

The controller validates initial content against the registry and starts with empty history; opening adds no history entry. Malformed JSON throws FormatException. Install the extensions that register the document's custom shapes.

To load into a live controller, call controller.load. expectedRevision is optional: omit it to replace without a conflict check, or capture the revision before waiting for a file or server response so a document that changed meanwhile is denied with DiagramDenialCode.conflict:

dart
final expectedRevision = controller.document.revision;
final json = await loadFile(); // Supplied by your application.

final result = controller.edits.load(
  json,
  format: DiagramJsonCodec.new,
  expectedRevision: expectedRevision,
);
if (!result.allowed) {
  showMessage(result.reason ?? 'Import was not allowed.');
}

Without format, data must already be a DiagramDocument: controller.edits.load(document, expectedRevision: ...).

Replacement is one verb, performed like any other edit. It validates the imported content against the diagram's registry, consults the beforeChange rule, clears the current selection and emits one completed change with import origin. Undo restores the previous document and selection; redo restores the imported document. Current camera and options remain in place. Imported revision numbers are normalized to the diagram's monotonic revision sequence. An import replaces the whole document, so it is denied with DiagramDenialCode.conflict while another controller is previewing an edit.

A different document ID represents a different identity scope. Even if both documents contain a shape named shape-1, the change delta reports removal from the old document and insertion into the new one.

Rejection and recovery ​

Stage / conditionResultRecovery
load or open with DiagramJsonCodec.new: malformed JSON, missing fields, invalid enum/value, or a version other than the current oneFormatExceptionCorrect the input or use a reader supporting that format. The diagram has not been changed.
load: stale expectedRevisionEditResult with code == DiagramDenialCode.conflictResolve the conflict or reload. Do not blindly retry with the newest revision. Publication and existing redo remain unchanged.
Candidate fails registry or geometry validationValidation exceptionCorrect the candidate or use its intended registry. The existing publication and history remain unchanged.
The beforeChange rule denies importEditResult with allowed == falseDisplay reason if provided. No replacement, completed-change event or history entry is added.
Replacement during this controller's own interactionStateErrorCommit or cancel the interaction first; import cannot take over its lease.
Another controller is previewing an editEditResult with code == DiagramDenialCode.conflictRetry after that controller commits or cancels.
Controller already disposedStateErrorUse a live controller.

load with a format decodes before attempting replacement, so a malformed file reports a format error even if its captured revision is also stale. The controller decodes, then validates and replaces the candidate once.

Successful replacement during text editing closes the old editor. A stale text client cannot mutate reused IDs in the imported document.

Opening data as a new DiagramController validates its snapshot but does not emit an import operation, run rules or replay creation events. Authorizing access to the initial snapshot belongs to the host.

Current boundaries ​

This API replaces a complete snapshot. It does not merge two documents, remap pasted IDs or synchronize concurrent agents. Clipboard verbs (copy, paste) retain their separate merge semantics while using the same admission.

PNG output and SVG scene output are available; full-content SVG and PDF output remain incomplete. Runtime animation bindings are not imported or reset by snapshot replacement; hosts that bind presentation to document identity should update those bindings from the import change event.

Format version ​

The JSON format is version 1: DiagramJsonCodec.currentVersion is 1, the codec writes version 1, and a document with any other version throws FormatException. The JSON format page specifies every key, what the decoder rejects and what each extension adds.

Standalone text ​

TextElement is written as kind: text, with frame, rotation, parent, stacking order, required story and sizing mode. Run colors persist directly. Paragraph and run attributes an extension owns, such as rich text's list kinds and marks, are written by that text's DiagramTextCodec inside a {type, version, data} envelope; decoding them requires the owning extension to be installed. Shape-only fields are absent; the codec rejects shape-specific fields on kind: text records.

Object lock persistence ​

Every shape, text element, and connector carries a required boolean isLocked. Copies preserve the flag unless explicitly changed; malformed flags are rejected. The diagram enforces locks for new changes and inherited ownership. The inspector exposes a lock toggle and hides editing fields while locked. Undo/redo replays admitted history and still consults host admission.

Label borders ​

Badge labels carry required, validated stroke, strokeWidth, and strokeStyle fields. Plain labels cannot carry decoration.

Shape values ​

Every shape requires a values object, empty when unused. It persists with history and copies. A shape's per-kind data lives here too: a path's path, a drawing's ink, an image's image and a polygon's sides, each written by its value type's toValue(). Values support null, booleans, strings, finite numbers, lists and string-keyed maps. Nested data is copied and frozen; widgets and callbacks cannot be serialized.

Files with missing, null or non-object values are rejected, as are non-finite numeric values. The codec preserves custom keys without interpreting them; the restoring registry and diagram admission own their semantic validation. Text elements do not carry shape values.