Five Years of json-tool and 3,000 Active Users Later

Published: (February 3, 2026 at 02:54 PM EST)
6 min read
Source: Dev.to

Source: Dev.to

3k users?

The number of weekly active users is fetched from the Snapcraft channel distribution. The “3k” comes from there, however the web version also contributes.

The Problem

In 2021, my workflow involved frequent JSON manipulation: debugging API responses, integrating with third‑party services, and inspecting data structures. Like many developers, I defaulted to Googling “JSON prettier” and using whatever website appeared first. Tools like JSON Formatter and JSON Pretty Print worked fine, but they came with a cost:

  • ads everywhere
  • no transparency about data handling
  • zero guarantee that my JSON strings weren’t being logged somewhere

For personal projects this was mildly annoying. For work involving production data or sensitive information, it felt irresponsible. ThoughtWorks Tech Radar volume 27 later highlighted this exact concern, warning developers about formatting tools that don’t comply with data‑jurisdiction requirements.

I wanted something different: a tool that collected no data, showed no ads, and remained transparent through open‑source code. No complex feature set—just reliable JSON formatting that respected user privacy. That’s how json‑tool was born.

First Implementation

The initial implementation was straightforward:

  • Electron app built with React
  • Published to snapcraft.io
  • Outside‑in TDD using Cypress and React Testing Library (a practice I’ve written about to ensure core functionality worked reliably from the start)

The first version worked well for typical use cases. Developers could:

  1. Paste JSON from logs
  2. Validate it
  3. Format it with custom spacing
  4. Copy the result back to the clipboard

The privacy‑first approach meant enterprise developers working with sensitive data could use it without corporate‑firewall concerns, and most importantly it runs offline once installed via Snapcraft.

The Freeze

Then reality hit: users started pasting JSON strings larger than 1 MB.

The application froze—completely. The UI became unresponsive, buttons stopped working, and the whole experience fell apart.

Through benchmarking with console.time() / console.timeEnd(), I traced the bottleneck to the JSON parsing and formatting happening on the main thread. JavaScript’s single‑threaded nature, combined with the event‑loop execution model, meant any blocking operation would freeze the entire UI.

Enter Web Workers

According to Ido Green’s book “Web Workers: Multithreaded Programs in JavaScript”, if your web application needs to complete a task that takes more than 150 ms, you should consider using Web Workers. Green specifically lists “encoding/decoding a large string” as a primary use case—exactly what json‑tool was doing.

Architecture Shift

Before (synchronous)

const onJsonChange = useCallback(async (value: string) => {
  setError('');
  if (!spacing) return;

  try {
    if (value) {
      JSON.parse(value);
    }
  } catch (e: any) {
    setError('invalid json');
  }

  let format = new Formatter(value, 2);
  const parseSpacing = parseInt(spacing);
  if (!isNaN(parseSpacing)) {
    format = new Formatter(value, parseSpacing);
  }

  const result = await format.format();
  setOriginalResult(value);
  setResult(result);
}, [spacing]);

After (event‑driven, off‑loading to a worker)

// UI thread
const onChange = (eventValue: string, eventSpacing: string) => {
  if (worker.current) {
    worker.current.postMessage({
      jsonAsString: eventValue,
      spacing: eventSpacing,
    });
  }
  setOriginalResult(eventValue);
  setInProgress(true);
};
// worker.js
importScripts('https://unpkg.com/format-to-json@2.1.2/fmt2json.min.js');

if (typeof importScripts === 'function') {
  addEventListener('message', async (event) => {
    const { jsonAsString: value, spacing } = event.data;

    if (value) {
      const format = await fmt2json(value, {
        expand: true,
        escape: false,
        indent: parseInt(spacing),
      });

      try {
        JSON.parse(value);
      } catch (e) {
        postMessage({
          error: true,
          originalJson: value,
          result: format.result,
        });
        return;
      }

      postMessage({
        error: false,
        originalJson: value,
        result: format.result,
      });
    }
  });
}

Did this improve raw computation time? No—the actual processing time remained the same. But the user experience was transformed. The UI stays responsive, and users can continue interacting while the worker does the heavy lifting.

Takeaways

  • Web Workers are invaluable for off‑loading CPU‑intensive tasks (e.g., parsing/formatting large JSON strings).
  • Even if the algorithmic runtime doesn’t change, moving work off the main thread prevents UI freezes.
  • A privacy‑first, offline‑first approach can attract a sizable user base, especially in enterprise contexts.
  • Continuous performance monitoring (benchmarks, profiling) is essential as usage patterns evolve.

References

  • Green, Ido. Web Workers: Multithreaded Programs in JavaScript.
  • ThoughtWorks Tech Radar, Volume 27.

Event‑Driven System with a Dedicated Worker

const onChange = (eventValue: string, eventSpacing: string) => {
  if (worker.current) {
    worker.current.postMessage({
      jsonAsString: eventValue,
      spacing: eventSpacing,
    });
  }
  setOriginalResult(eventValue);
  setInProgress(true);
};

The Worker Handles Parsing & Formatting

importScripts('https://unpkg.com/format-to-json@2.1.2/fmt2json.min.js');

if (typeof importScripts === 'function') {
  addEventListener('message', async (event) => {
    const { jsonAsString: value, spacing } = event.data;

    if (!value) return;

    const format = await fmt2json(value, {
      expand: true,
      escape: false,
      indent: parseInt(spacing, 10),
    });

    try {
      JSON.parse(value);
    } catch (e) {
      postMessage({
        error: true,
        originalJson: value,
        result: format.result,
      });
      return;
    }

    postMessage({
      error: false,
      originalJson: value,
      result: format.result,
    });
  });
}

Did this improve performance?
No. The actual computation time stayed the same, but the user experience was transformed.

Web Workers to the Rescue

Exploring architecture changes and performance implications.

Maintaining TDD Discipline

  • I started with outside‑in TDD and refused to compromise.
  • Introducing workers added testing complexity: the Worker API exists in browsers but is undefined in Jest’s jsdom environment, causing the test suite to break.

Solution: jsdom-worker

  1. Install

    npm i jsdom-worker
  2. Import in setupTests.ts

    import 'jsdom-worker';

The library mocks the Worker API for jsdom. It doesn’t create real threads, and it has limitations (no shared workers, must use the Blob pattern).

Example Test (Behavior‑Driven)

it('should format json from uploaded file', async () => {
  const file = new File(['{"a":"b"}'], 'hello.json', {
    type: 'application/json',
  });

  const { getByTestId } = render();

  await act(async () => {
    await userEvent.upload(getByTestId('upload-json'), file);
  });

  await waitFor(() => {
    expect(getByTestId('raw-result')).toHaveValue(`{
  "a": "b"
}`);
  });
});
  • The test describes what should happen, not how.
  • It verifies that uploaded JSON gets formatted correctly, regardless of whether a worker is used.
  • Lesson: Web workers are implementation details; tests should focus on user‑facing behavior.

Live‑Coding Session

  • Refactored synchronous code to use workers while preserving test coverage.
  • Demonstrated TDD in a coding‑dojo setting.

Trade‑offs of the Worker Implementation

  • Pros: Eliminated UI freezing.
  • Cons: Introduced an event‑driven architecture, requiring understanding of:
    • Worker lifecycle management
    • Message passing
    • Off‑main‑thread execution

Mitigation: Keep the worker logic isolated; the rest of the app stays simple (React components, straightforward state, minimal abstractions). Documentation contains the necessary details.

Open‑Source Maintenance Realities

  • Dependencies need regular updates.
  • Security vulnerabilities must be patched.
  • Browser APIs evolve; testing libraries change.

Sustainability Over Rapid Development

  • Tech stack: React, TypeScript, Tailwind, CodeMirror – all stable, well‑maintained projects with large communities.
  • Avoid cutting‑edge dependencies that might be abandoned.

Update cadence: Batches of updates occur when I’m actively using the tool and notice a need. This is a community‑driven project, not a business with SLA guarantees.

Lessons Learned About Open Source

  1. Transparency builds trust – Users can audit the code, verify no tracking, data exfiltration, or ads.
  2. Focused tools solve problems betterjson‑tool does one thing well: format JSON with privacy guarantees.
  3. Test‑Driven Development pays dividends – The initial TDD investment allowed safe refactoring to workers years later.

Future Roadmap (Steady Improvements)

  • Performance: Optimize handling of extremely large JSON files (> 100 MB).
  • Accessibility: Add screen‑reader support and improve keyboard navigation.
  • Editor sync: Keep both panes scrolling simultaneously.
  • Dependencies: Upgrade to the latest CodeMirror version, keep React and TypeScript up‑to‑date.

No plans to monetize, add analytics, or expand beyond the core purpose. Sponsorships are welcome via GitHub Sponsors.

Closing Thought

The most rewarding part of this journey wasn’t reaching 3,000 users; it was building something that solves a real problem with simplicity, reliability, and privacy at its core.

without compromising on principles. No tracking. No ads. No data collection.
Just a tool that does what it promises and respects its users.

The source code for json‑tool remains available at its repository, and the tool itself can be downloaded from the Snapcraft store. It’s been five years—here’s to many more years of building software that respects privacy and serves developers well.

Back to Blog

Related posts

Read more »

Crowd funding platform

GitHub Copilot CLI Challenge Submission This is a submission for the GitHub Copilot CLI Challengehttps://dev.to/challenges/github-copilot. Repository bash git...