or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

algorithms.mdgraph.mdindex.mdjson.md
tile.json

json.mddocs/

JSON Serialization

Complete graph serialization and deserialization functionality for persistence, network transfer, and interoperability with other systems.

Capabilities

Graph Serialization

Converts graph instances to JSON-serializable objects preserving all graph data and metadata.

/**
 * Converts graph to JSON-serializable object
 * @param g - Graph instance to serialize
 * @returns Object containing complete graph representation
 */
function write(g: Graph): SerializedGraph;

interface SerializedGraph {
  options: {
    directed: boolean;
    multigraph: boolean;
    compound: boolean;
  };
  nodes: SerializedNode[];
  edges: SerializedEdge[];
  value?: any; // Graph-level value if set
}

interface SerializedNode {
  v: string;          // Node ID
  value?: any;        // Node value/label if set
  parent?: string;    // Parent node ID (compound graphs only)
}

interface SerializedEdge {
  v: string;          // Source node ID
  w: string;          // Target node ID
  name?: string;      // Edge name (multigraphs only)
  value?: any;        // Edge value/label if set
}

Usage Examples:

import { Graph, json } from "graphlib";

// Create and populate graph
const g = new Graph({ compound: true });
g.setGraph({ title: "Sample Graph", version: "1.0" });

// Add hierarchical nodes
g.setNode("group1", { type: "container" });
g.setNode("item1", { name: "First Item", data: 42 });
g.setNode("item2", { name: "Second Item", data: 73 });
g.setParent("item1", "group1");
g.setParent("item2", "group1");

// Add edges with metadata
g.setEdge("item1", "item2", { 
  relationship: "depends_on", 
  strength: 0.8 
});

// Serialize to JSON
const serialized = json.write(g);
console.log(JSON.stringify(serialized, null, 2));

Output format:

{
  "options": {
    "directed": true,
    "multigraph": false,
    "compound": true
  },
  "nodes": [
    {
      "v": "group1",
      "value": { "type": "container" }
    },
    {
      "v": "item1", 
      "value": { "name": "First Item", "data": 42 },
      "parent": "group1"
    },
    {
      "v": "item2",
      "value": { "name": "Second Item", "data": 73 },
      "parent": "group1"
    }
  ],
  "edges": [
    {
      "v": "item1",
      "w": "item2",
      "value": { "relationship": "depends_on", "strength": 0.8 }
    }
  ],
  "value": { "title": "Sample Graph", "version": "1.0" }
}

Graph Deserialization

Creates graph instances from JSON objects, restoring all graph properties and data.

/**
 * Creates graph from JSON object
 * @param json - Serialized graph object (format from write())
 * @returns New Graph instance with restored data
 */
function read(json: SerializedGraph): Graph;

Usage Examples:

// Restore graph from JSON
const restoredGraph = json.read(serialized);

// Verify restoration
console.log(restoredGraph.isCompound()); // true
console.log(restoredGraph.graph()); // { title: "Sample Graph", version: "1.0" }
console.log(restoredGraph.nodes()); // ["group1", "item1", "item2"]
console.log(restoredGraph.parent("item1")); // "group1"
console.log(restoredGraph.edge("item1", "item2")); 
// { relationship: "depends_on", strength: 0.8 }

Serialization Patterns

Complete Graph Persistence

// Save graph to file/database
const saveGraph = (graph, filename) => {
  const serialized = json.write(graph);
  const jsonString = JSON.stringify(serialized, null, 2);
  // Save jsonString to file or database
  return jsonString;
};

// Load graph from file/database
const loadGraph = (jsonString) => {
  const parsed = JSON.parse(jsonString);
  return json.read(parsed);
};

Network Transfer

// Client-side: send graph to server
const sendGraphToServer = async (graph) => {
  const payload = json.write(graph);
  
  const response = await fetch('/api/graphs', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload)
  });
  
  return response.json();
};

// Server-side: receive and process graph
const processReceivedGraph = (jsonPayload) => {
  const graph = json.read(jsonPayload);
  
  // Process graph...
  const result = analyzeGraph(graph);
  
  // Send result back
  return json.write(result);
};

Incremental Updates

// Create diff between graph versions
const createGraphDiff = (oldGraph, newGraph) => {
  const oldSerialized = json.write(oldGraph);
  const newSerialized = json.write(newGraph);
  
  return {
    old: oldSerialized,
    new: newSerialized,
    timestamp: Date.now()
  };
};

// Apply updates to existing graph
const updateGraph = (currentGraph, updates) => {
  // Serialize current state
  let current = json.write(currentGraph);
  
  // Apply node updates
  updates.nodes?.forEach(nodeUpdate => {
    const existingNode = current.nodes.find(n => n.v === nodeUpdate.v);
    if (existingNode) {
      Object.assign(existingNode, nodeUpdate);
    } else {
      current.nodes.push(nodeUpdate);
    }
  });
  
  // Apply edge updates
  updates.edges?.forEach(edgeUpdate => {
    const existingEdge = current.edges.find(e => 
      e.v === edgeUpdate.v && e.w === edgeUpdate.w && e.name === edgeUpdate.name
    );
    if (existingEdge) {
      Object.assign(existingEdge, edgeUpdate);
    } else {
      current.edges.push(edgeUpdate);
    }
  });
  
  return json.read(current);
};

Data Format Compatibility

Version Handling

// Add version info to serialized graphs
const writeVersionedGraph = (graph, version = "1.0") => {
  const serialized = json.write(graph);
  return {
    ...serialized,
    formatVersion: version,
    timestamp: new Date().toISOString()
  };
};

// Handle different format versions
const readVersionedGraph = (data) => {
  const version = data.formatVersion || "1.0";
  
  switch (version) {
    case "1.0":
      return json.read(data);
    case "2.0":
      // Handle newer format
      return json.read(migrateFromV2(data));
    default:
      throw new Error(`Unsupported format version: ${version}`);
  }
};

Interoperability

// Convert to other graph formats
const convertToD3Format = (graph) => {
  const serialized = json.write(graph);
  
  const nodes = serialized.nodes.map(node => ({
    id: node.v,
    ...node.value
  }));
  
  const links = serialized.edges.map(edge => ({
    source: edge.v,
    target: edge.w,
    ...edge.value
  }));
  
  return { nodes, links };
};

// Convert from DOT format (conceptual)
const convertFromDot = (dotString) => {
  const parsed = parseDotString(dotString); // External parser
  
  const serialized = {
    options: {
      directed: parsed.directed,
      multigraph: false,
      compound: false
    },
    nodes: parsed.nodes.map(n => ({ v: n.id, value: n.attributes })),
    edges: parsed.edges.map(e => ({ 
      v: e.from, 
      w: e.to, 
      value: e.attributes 
    }))
  };
  
  return json.read(serialized);
};

Advanced Serialization Features

Selective Serialization

// Serialize only specific node/edge properties
const writeMinimal = (graph) => {
  const full = json.write(graph);
  
  return {
    ...full,
    nodes: full.nodes.map(node => ({
      v: node.v,
      // Only keep essential properties
      value: node.value ? { 
        id: node.value.id, 
        type: node.value.type 
      } : undefined
    })),
    edges: full.edges.map(edge => ({
      v: edge.v,
      w: edge.w,
      name: edge.name,
      // Only keep weight property
      value: edge.value?.weight
    }))
  };
};

Custom Serialization

// Custom serialization with data transformation
const writeWithTransform = (graph, transformer) => {
  const serialized = json.write(graph);
  
  if (transformer.nodes) {
    serialized.nodes = serialized.nodes.map(transformer.nodes);
  }
  
  if (transformer.edges) {
    serialized.edges = serialized.edges.map(transformer.edges);
  }
  
  if (transformer.graph && serialized.value) {
    serialized.value = transformer.graph(serialized.value);
  }
  
  return serialized;
};

// Usage
const compressed = writeWithTransform(graph, {
  nodes: (node) => ({
    v: node.v,
    d: compressNodeData(node.value) // Custom compression
  }),
  edges: (edge) => ({
    v: edge.v,
    w: edge.w,
    w: compressEdgeWeight(edge.value) // Custom compression
  })
});

Performance Considerations

Memory Usage

  • Serialization creates complete copy of graph data
  • Large graphs may require streaming serialization for memory efficiency
  • Consider selective serialization for bandwidth-limited scenarios

Serialization Speed

  • write() time complexity: O(V + E) where V = nodes, E = edges
  • read() time complexity: O(V + E)
  • JSON.stringify/parse adds additional overhead

Optimization Tips

// For large graphs, consider chunked processing
const writeChunked = (graph, chunkSize = 1000) => {
  const serialized = json.write(graph);
  
  const chunks = [];
  const nodes = serialized.nodes;
  const edges = serialized.edges;
  
  for (let i = 0; i < nodes.length; i += chunkSize) {
    chunks.push({
      type: 'nodes',
      data: nodes.slice(i, i + chunkSize)
    });
  }
  
  for (let i = 0; i < edges.length; i += chunkSize) {
    chunks.push({
      type: 'edges', 
      data: edges.slice(i, i + chunkSize)
    });
  }
  
  return {
    options: serialized.options,
    value: serialized.value,
    chunks
  };
};

Error Handling

JSON serialization errors are generally related to data types:

  • TypeError: Non-serializable values (functions, symbols, undefined)
  • RangeError: Circular references in node/edge data
  • SyntaxError: Invalid JSON during deserialization
// Safe serialization with error handling
const safeWrite = (graph) => {
  try {
    const serialized = json.write(graph);
    JSON.stringify(serialized); // Test serializability
    return serialized;
  } catch (error) {
    console.error("Serialization failed:", error);
    
    // Return minimal serializable version
    return {
      options: {
        directed: graph.isDirected(),
        multigraph: graph.isMultigraph(),
        compound: graph.isCompound()
      },
      nodes: graph.nodes().map(v => ({ v })),
      edges: graph.edges().map(e => ({ v: e.v, w: e.w, name: e.name }))
    };
  }
};