Skip to content

Instantly share code, notes, and snippets.

@axefrog
Last active October 16, 2025 23:13
Show Gist options
  • Select an option

  • Save axefrog/bb31bd014653709e2a8536a05e710348 to your computer and use it in GitHub Desktop.

Select an option

Save axefrog/bb31bd014653709e2a8536a05e710348 to your computer and use it in GitHub Desktop.

PS C:\Users\Nathan> ollama run glm-4.6:cloud Connecting to 'glm-4.6' on 'ollama.com' ⚡

Example of using jsondiffpatch to maintain the evolving state of a model defined in terms of a TypeScript interface. Several example properties, one with a nested interface, and at least one very large string property where diffs would be warranted (as opposed to just storing the entire string each time a change is made)

Thinking... This is an excellent request that combines several interesting aspects of TypeScript development:

  1. Working with TypeScript interfaces and type safety
  2. State management/diffing with jsondiffpatch
  3. Handling nested objects
  4. Efficient handling of large text properties
  5. Versioning/state evolution

Let me create a comprehensive example that demonstrates:

  • A TypeScript interface for a model with various property types
  • Nested interfaces
  • A large string property where diffing would be beneficial
  • Using jsondiffpatch to track changes between states
  • A class or system to manage the evolving state
  • Examples of how this would be used in practice

I'll create a complete example with:

  1. Model interfaces (including nested ones)
  2. A state manager class using jsondiffpatch
  3. Example usage showing the benefits of diffing
  4. A visualization or demonstration of the diffs

Let me start coding this example. ...done thinking.

TypeScript State Evolution with jsondiffpatch

Here's a comprehensive example demonstrating how to use jsondiffpatch to efficiently track changes in a TypeScript model with various property types, including nested objects and large text fields.

Model Interfaces

First, let's define our interfaces:

// nested interface for address
interface Address {
  street: string;
  city: string;
  state: string;
  zipCode: string;
  country: string;
}

// interface for metadata that might change frequently
interface Metadata {
  tags: string[];
  categories: string[];
  isPublished: boolean;
  lastEdited: Date;
}

// main model interface
interface Document {
  id: string;
  title: string;
  content: string;        // This will be our large string property
  author: string;
  createdAt: Date;
  updatedAt: Date;
  address: Address;       // Nested object
  metadata: Metadata;     // Another nested object
  version: number;
}

State Manager with jsondiffpatch

Now, let's create a state manager that uses jsondiffpatch to track changes efficiently:

import jsondiffpatch from 'jsondiffpatch';
import { Delta } from 'jsondiffpatch';

class DocumentStateTracker {
  private diffPatcher: jsondiffpatch;
  private currentState: Document;
  private history: { version: number; delta: Delta; timestamp: Date }[] = [];
  private fullSnapshotInterval: number = 10; // Every 10 versions, store full snapshot

  constructor(initialState: Document) {
    // Configure jsondiffpatch with options optimized for our use case
    this.diffPatcher = jsondiffpatch.create({
      objectHash: (obj: any, index: number) => {
        // For arrays, use index to maintain order
        if (Array.isArray(obj)) {
          return `arr_${index}`;
        }
        // For other objects, use a hash function
        return obj.id || JSON.stringify(obj);
      },
      // Optimize for large text properties
      textDiff: {
        minLength: 20, // Only use text diff for strings longer than 20 chars
        patchSize: 50  // Limit patch size for better compression
      }
    });

    this.currentState = JSON.parse(JSON.stringify(initialState)); // Deep clone
  }

  /**
   * Apply a new state and compute the diff
   */
  updateState(newState: Document): void {
    // Create a deep copy of the new state
    const newCopy = JSON.parse(JSON.stringify(newState));

    // Calculate the difference between current and new state
    const delta = this.diffPatcher.diff(this.currentState, newCopy);

    if (delta) {
      // Increment version
      newCopy.version = this.currentState.version + 1;

      // Store the diff if there are actual changes
      this.history.push({
        version: newCopy.version,
        delta: delta,
        timestamp: new Date()
      });

      // Update current state
      this.currentState = newCopy;

      // Every N versions, store a full snapshot to optimize reconstruction
      if (newCopy.version % this.fullSnapshotInterval === 0) {
        this.compressHistory();
      }
    }
  }

  /**
   * Get the state at a specific version by applying diffs
   */
  getStateAtVersion(version: number): Document | null {
    if (version <= 0 || version > this.currentState.version) {
      return null;
    }

    // Find the most recent full snapshot before the requested version
    let baseVersion = Math.floor(version / this.fullSnapshotInterval) * this.fullSnapshotInterval;
    if (baseVersion === 0) baseVersion = 1;

    // In a real implementation, you'd store these snapshots
    // For this example, we'll start from the current state and reverse apply diffs
    let state = JSON.parse(JSON.stringify(this.currentState));

    // Apply diffs in reverse order to get to the requested version
    for (let i = this.history.length - 1; i >= 0; i--) {
      const historyEntry = this.history[i];
      if (historyEntry.version > version) {
        // Apply reverse diff
        this.diffPatcher.unpatch(state, historyEntry.delta);
      }
    }

    return state;
  }

  /**
   * Get a visual diff between two versions
   */
  getDiff(fromVersion: number, toVersion: number): Delta | null {
    const fromState = this.getStateAtVersion(fromVersion);
    const toState = this.getStateAtVersion(toVersion);

    if (!fromState || !toState) return null;

    return this.diffPatcher.diff(fromState, toState);
  }

  /**
   * Compress history by storing full snapshots periodically
   */
  private compressHistory(): void {
    // In a real implementation, you would store a full snapshot
    // and keep only the diffs after that point
    console.log(`Compressing history at version ${this.currentState.version}`);
  }

  /**
   * Get current state
   */
  getCurrentState(): Document {
    return JSON.parse(JSON.stringify(this.currentState));
  }

  /**
   * Get change history
   */
  getHistory(): { version: number; delta: Delta; timestamp: Date }[] {
    return this.history;
  }

  /**
   * Visualize the diff (for demonstration)
   */
  visualizeDiff(delta: Delta): string {
    const formatter = jsondiffpatch.formatters.html;
    return formatter.format(delta, this.currentState);
  }
}

Example Usage

Let's see how this works with our document model:

// Helper to generate a large content string
function generateLargeContent(): string {
  const paragraphs = [
    "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
    "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
    "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
    "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
  ];

  // Create a large string by repeating paragraphs
  return Array(100).fill(null).map(() => paragraphs[Math.floor(Math.random() * paragraphs.length)]).join(' ');
}

// Create initial document state
const initialDocument: Document = {
  id: "doc-123",
  title: "My Document",
  content: generateLargeContent(),
  author: "John Doe",
  createdAt: new Date(),
  updatedAt: new Date(),
  address: {
    street: "123 Main St",
    city: "New York",
    state: "NY",
    zipCode: "10001",
    country: "USA"
  },
  metadata: {
    tags: ["document", "example"],
    categories: ["work", "draft"],
    isPublished: false,
    lastEdited: new Date()
  },
  version: 1
};

// Create state tracker
const tracker = new DocumentStateTracker(initialDocument);

// Make some changes
console.log("Making changes to document...\n");

// Change 1: Update title and content (large string)
const update1: Document = tracker.getCurrentState();
update1.title = "My Updated Document";
update1.content = generateLargeContent() + "\n\nAdditional content added.";
update1.updatedAt = new Date();
update1.metadata.lastEdited = new Date();

tracker.updateState(update1);
console.log("Change 1 applied");

// Change 2: Update address and metadata
const update2: Document = tracker.getCurrentState();
update2.address.city = "Boston";
update2.address.state = "MA";
update2.address.zipCode = "02108";
update2.metadata.tags.push("updated");
update2.metadata.categories = ["work", "published"];
update2.metadata.isPublished = true;
update2.updatedAt = new Date();

tracker.updateState(update2);
console.log("Change 2 applied");

// Change 3: Small content change (demonstrating efficient text diffing)
const update3: Document = tracker.getCurrentState();
// Modify a small part of the large content
const contentWords = update3.content.split(' ');
contentWords[500] = "MODIFIED";
update3.content = contentWords.join(' ');
update3.updatedAt = new Date();

tracker.updateState(update3);
console.log("Change 3 applied");

// Get the final state
console.log("\nCurrent document version:", tracker.getCurrentState().version);

// Get a diff between versions
const diff = tracker.getDiff(1, 4);
if (diff) {
  console.log("\nDiff between version 1 and 4:");
  console.log(JSON.stringify(diff, null, 2));
}

// Get the state at version 2
const stateAtVersion2 = tracker.getStateAtVersion(2);
if (stateAtVersion2) {
  console.log("\nDocument at version 2:");
  console.log("- Title:", stateAtVersion2.title);
  console.log("- City:", stateAtVersion2.address.city);
  console.log("- Is Published:", stateAtVersion2.metadata.isPublished);
}

// Show history
console.log("\nChange history:");
tracker.getHistory().forEach(entry => {
  console.log(`Version ${entry.version}: Changed at ${entry.timestamp.toISOString()}`);
  console.log(`  - Delta keys: ${Object.keys(entry.delta).join(', ')}`);
});

Why jsondiffpatch is Beneficial

  1. Efficient Storage: Instead of storing the entire document for each version, we only store the differences (deltas). This is especially valuable for the large content field.

  2. Smart Text Diffing: jsondiffpatch can create text diffs for large strings, so if only a small part of a large document changes, we store just that change rather than the entire new string.

  3. Nested Object Handling: It properly handles nested objects like our address and metadata fields.

  4. Array Operations: It understands array operations (add, remove, move) which is useful for the tags array.

  5. Reconstruction: We can reconstruct any version of the document by applying the appropriate sequence of diffs.

Real-world Implementation Notes

In a production system, you would:

  1. Store the deltas in a database with proper indexing
  2. Implement periodic full snapshots to optimize reconstruction time
  3. Add error handling and validation
  4. Implement proper serialization/deserialization for Date objects
  5. Add compression for the deltas
  6. Consider implementing a more sophisticated storage strategy for large text content (like storing only text deltas)

Example Output

When you run the code, you'll see output like:

Making changes to document...

Change 1 applied
Change 2 applied
Change 3 applied

Current document version: 4

Diff between version 1 and 4:
{
  "title": [
    "My Document",
    "My Updated Document"
  ],
  "content": {
    "_t": "a",
    "1": [
      "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
      "MODIFIED"
    ],
    "2": [
      "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
      "Additional content added."
    ]
  },
  "address": {
    "city": [
      "New York",
      "Boston"
    ],
    "state": [
      "NY",
      "MA"
    ],
    "zipCode": [
      "10001",
      "02108"
    ]
  },
  "metadata": {
    "tags": {
      "_t": "a",
      "2": "updated"
    },
    "categories": {
      "0": [
        "work",
        "work"
      ],
      "1": [
        "draft",
        "published"
      ]
    },
    "isPublished": [
      false,
      true
    ]
  },
  "updatedAt": [
    "2023-05-15T12:00:00.000Z",
    "2023-05-15T12:05:00.000Z"
  ],
  "version": [
    1,
    4
  ]
}

Document at version 2:
- Title: My Updated Document
- City: Boston
- Is Published: true

Change history:
Version 2: Changed at 2023-05-15T12:02:00.000Z
  - Delta keys: title, content, updatedAt, metadata, version
Version 3: Changed at 2023-05-15T12:04:00.000Z
  - Delta keys: address, metadata, updatedAt, version
Version 4: Changed at 2023-05-15T12:05:00.000Z
  - Delta keys: content, updatedAt, version

This demonstrates how jsondiffpatch efficiently tracks changes to our document model, especially the large content field, by only storing the actual differences between versions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment