Serialization
Save and load complete graph state as JSON. Preserves nodes, edges, positions, and custom data.
Overview
FlowState provides built-in serialization to save and restore graphs. All node parameters marked with [Parameter] are automatically serialized.
Quick Start
// Save
string json = await graph.SerializeAsync();
await File.WriteAllTextAsync("my-graph.json", json);
// Load
string json = await File.ReadAllTextAsync("my-graph.json");
await graph.DeserializeAsync(json);
Data Classes
GraphData
Complete graph state.
Properties:
Canvas(CanvasProperties) - Viewport stateNodes(List) - All nodes Edges(List) - All edges
NodeProperties
Node state.
Properties:
Type(string) - Fully qualified type nameId(string) - Node IDX,Y(double) - PositionData(Dictionary<string, StoredProperty>) - Parameter values
EdgeProperties
Edge state.
Properties:
Id(string) - Edge IDFromNodeId(string) - Source nodeToNodeId(string) - Target nodeFromSocketName(string) - Source socketToSocketName(string) - Target socket
CanvasProperties
Canvas viewport state.
Properties:
Zoom(double) - Zoom levelOffsetX,OffsetY(double) - Pan offsetMinZoom,MaxZoom(double) - Zoom limitsIsReadOnly(bool) - Read-only state
Serialization Methods
SerializeAsync
Serializes graph to JSON string.
Signature: ValueTask<string> SerializeAsync()
string json = await graph.SerializeAsync();
Console.WriteLine(json);
GetSerializableObjectAsync
Gets structured graph data.
Signature: ValueTask<GraphData> GetSerializableObjectAsync()
var data = await graph.GetSerializableObjectAsync();
Console.WriteLine($"Graph has {data.Nodes.Count} nodes and {data.Edges.Count} edges");
// Access node data
foreach (var node in data.Nodes)
{
Console.WriteLine($"Node: {node.Type} at ({node.X}, {node.Y})");
}
Deserialization Methods
DeserializeAsync (JSON)
Loads graph from JSON string.
Signature: ValueTask DeserializeAsync(string data)
string json = await File.ReadAllTextAsync("graph.json");
await graph.DeserializeAsync(json);
DeserializeAsync (Object)
Loads graph from GraphData object.
Signature: ValueTask DeserializeAsync(GraphData graphData)
GraphData data = GetGraphDataFromDatabase();
await graph.DeserializeAsync(data);
Custom Node Data
All [Parameter] properties are automatically serialized:
public partial class ConfigNode : FlowNodeBase
{
[Parameter]
public string Name { get; set; } = "Default";
[Parameter]
public int Count { get; set; } = 10;
[Parameter]
public bool Enabled { get; set; } = true;
// These will all be saved and restored automatically
}
Supported parameter types:
- Primitives:
int,float,double,bool,string DateTime,TimeSpan,Guid- Arrays and Lists of supported types
- Custom classes (JSON-serializable)
Complete Save/Load Example
@using FlowState.Components
@using FlowState.Models
@using System.IO
<div class="toolbar">
<button @onclick="SaveGraph">💾 Save</button>
<button @onclick="LoadGraph">📂 Load</button>
<button @onclick="ExportGraph">⬇️ Export</button>
<button @onclick="ImportGraph">⬆️ Import</button>
</div>
<FlowCanvas Graph="graph" Height="calc(100vh - 60px)" Width="100vw">
<BackgroundContent>
<FlowBackground class="grid-bg"/>
</BackgroundContent>
</FlowCanvas>
@code {
FlowGraph graph = new();
private string savedData = "{}";
async Task SaveGraph()
{
// Save to memory
savedData = await graph.SerializeAsync();
Console.WriteLine("Graph saved to memory");
}
async Task LoadGraph()
{
// Load from memory
await graph.DeserializeAsync(savedData);
Console.WriteLine("Graph loaded from memory");
}
async Task ExportGraph()
{
// Export to file
string json = await graph.SerializeAsync();
await File.WriteAllTextAsync("my-graph.json", json);
Console.WriteLine("Graph exported to file");
}
async Task ImportGraph()
{
// Import from file
if (File.Exists("my-graph.json"))
{
string json = await File.ReadAllTextAsync("my-graph.json");
await graph.DeserializeAsync(json);
Console.WriteLine("Graph imported from file");
}
}
}
Browser Storage (Blazor WASM)
Save to browser localStorage:
@inject IJSRuntime JS
async Task SaveToLocalStorage()
{
string json = await graph.SerializeAsync();
await JS.InvokeVoidAsync("localStorage.setItem", "myGraph", json);
}
async Task LoadFromLocalStorage()
{
string? json = await JS.InvokeAsync<string?>("localStorage.getItem", "myGraph");
if (!string.IsNullOrEmpty(json))
{
await graph.DeserializeAsync(json);
}
}
Database Storage
Save to database:
// Save
var graphData = await graph.GetSerializableObjectAsync();
var entity = new GraphEntity
{
Id = Guid.NewGuid(),
Name = "My Graph",
Data = JsonSerializer.Serialize(graphData),
CreatedAt = DateTime.UtcNow
};
await database.Graphs.AddAsync(entity);
await database.SaveChangesAsync();
// Load
var entity = await database.Graphs.FindAsync(graphId);
if (entity != null)
{
await graph.DeserializeAsync(entity.Data);
}
JSON Format
Example serialized graph:
{
"Canvas": {
"Zoom": 1.0,
"OffsetX": 0,
"OffsetY": 0,
"MinZoom": 0.2,
"MaxZoom": 2.0,
"IsReadOnly": false
},
"Nodes": [
{
"Type": "MyNamespace.InputNode, MyAssembly",
"Id": "node-123",
"X": 100,
"Y": 100,
"Data": {
"Value": {
"Type": "System.Single",
"Value": 42.5
}
}
}
],
"Edges": [
{
"Id": "edge-456",
"FromNodeId": "node-123",
"ToNodeId": "node-789",
"FromSocketName": "Output",
"ToSocketName": "Input"
}
]
}
Versioning
For graph versioning, add metadata:
public class VersionedGraphData
{
public int Version { get; set; } = 1;
public DateTime SavedAt { get; set; }
public string Author { get; set; } = "";
public GraphData Graph { get; set; } = null!;
}
// Save
var versionedData = new VersionedGraphData
{
Version = 1,
SavedAt = DateTime.UtcNow,
Author = "User",
Graph = await graph.GetSerializableObjectAsync()
};
string json = JsonSerializer.Serialize(versionedData);
// Load
var versionedData = JsonSerializer.Deserialize<VersionedGraphData>(json);
if (versionedData.Version == 1)
{
await graph.DeserializeAsync(versionedData.Graph);
}
Migration
Handle breaking changes with migration:
async Task LoadWithMigration(string json)
{
var data = JsonSerializer.Deserialize<GraphData>(json);
// Migrate old node types
foreach (var node in data.Nodes)
{
if (node.Type == "OldNamespace.OldNode, OldAssembly")
{
node.Type = "NewNamespace.NewNode, NewAssembly";
}
}
await graph.DeserializeAsync(data);
}
Clear Before Load
Deserialization automatically clears the graph:
// No need to manually clear
await graph.DeserializeAsync(json);
// Equivalent to:
await graph.ClearAsync();
await graph.DeserializeAsync(json);
Handling Errors
try
{
await graph.DeserializeAsync(json);
}
catch (JsonException ex)
{
Console.WriteLine($"Invalid JSON: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"Load failed: {ex.Message}");
}
See Also
- FlowGraph - Serialize/Deserialize methods
- FlowNodeBase - Parameter serialization
- Getting Started - Save/Load example