CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-graphlib

A directed and undirected multi-graph library with comprehensive graph algorithms

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

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 }))
    };
  }
};

docs

algorithms.md

graph.md

index.md

json.md

tile.json