jsoncompat 0.3.0

JSON Schema Compatibility Checker
Documentation
import { createFileRoute } from "@tanstack/react-router";
// Get the final URL of the wasm binary from Vite at build time.
// eslint-disable-next-line import/no-unresolved
import wasmUrl from "jsoncompat/jsoncompat_wasm_bg.wasm?url";
import { useState } from "react";
import init, { generate_value } from "jsoncompat";

const DEFAULT_SCHEMA = `{
  "type": "object",
  "properties": {
    "name": { "type": "string" },
    "age": { "type": "integer", "minimum": 18 }
  },
  "required": ["name"]
}`;

export const Route = createFileRoute("/fuzzer")({
  component: FuzzerPage,
});

function FuzzerPage() {
  const [schema, setSchema] = useState(DEFAULT_SCHEMA);
  const [depth, setDepth] = useState(5);
  const [examples, setExamples] = useState<string[]>([]);
  const [numExamples, setNumExamples] = useState(5);
  const [error, setError] = useState<string | null>(null);

  async function runGenerate() {
    setError(null);
    setExamples([]);
    try {
      await init(wasmUrl); // TODO: only do once
      const vals: string[] = [];
      for (let i = 0; i < numExamples; i++) {
        // eslint-disable-next-line no-await-in-loop
        const v = await generate_value(schema, depth);
        vals.push(v);
      }
      setExamples(vals);
    } catch (err) {
      console.log(err);
      setError((err as Error).message ?? String(err));
    }
  }

  function copyAll() {
    if (examples.length === 0) return;
    // JSON Lines (JSONL) – one JSON document per line.
    const jsonl = examples.join("\n");
    navigator.clipboard.writeText(jsonl);
  }

  return (
    <main className="mx-auto max-w-3xl px-4 py-8 space-y-6">
      <h1 className="mb-4 text-3xl font-bold">
        Value generator / schema fuzzer
      </h1>

      <label htmlFor="schema" className="mb-2 block font-medium">
        Schema
      </label>
      <textarea
        id="schema"
        className="h-64 w-full rounded-md border border-gray-300 p-2 font-mono text-sm"
        value={schema}
        onChange={(e) => setSchema(e.target.value)}
      />

      <div className="flex flex-wrap items-center gap-4">
        <label
          htmlFor="depth"
          className="flex items-center gap-1 text-sm font-medium text-gray-700"
        >
          Depth:
          <input
            id="depth"
            type="number"
            min="1"
            max="10"
            value={depth}
            onChange={(e) => setDepth(Number(e.target.value))}
            className="w-16 rounded-md border border-gray-300 p-1 text-right"
          />
        </label>

        <label
          htmlFor="num-ex"
          className="flex items-center gap-1 text-sm font-medium text-gray-700"
        >
          Examples:
          <input
            id="num-ex"
            type="number"
            min="1"
            max="20"
            value={numExamples}
            onChange={(e) => setNumExamples(Number(e.target.value))}
            className="w-16 rounded-md border border-gray-300 p-1 text-right"
          />
        </label>

        <button
          type="button"
          onClick={runGenerate}
          className="rounded bg-blue-600 px-4 py-2 font-medium text-white hover:bg-blue-700"
        >
          Generate
        </button>

        {examples.length > 0 && (
          <button
            type="button"
            onClick={copyAll}
            className="rounded border border-gray-300 bg-white px-4 py-2 text-sm shadow-sm hover:bg-gray-50"
          >
            Copy all
          </button>
        )}
      </div>

      {examples.length > 0 && (
        <div className="max-h-[40rem] space-y-4 overflow-y-auto rounded-md border border-gray-200 p-2">
          {examples.map((ex, idx) => (
            <pre
              // biome-ignore lint/suspicious/noArrayIndexKey: nothing else to use
              key={idx}
              className="max-h-40 overflow-auto rounded bg-gray-100 p-4 text-sm"
            >
              {ex}
            </pre>
          ))}
        </div>
      )}

      {error && <p className="mt-4 text-red-600">{error}</p>}
    </main>
  );
}