Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions components/utils/json-schema-validator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { validateJsonSchema } from "./json-schema-validator";

describe("validateJsonSchema", () => {
test("should validate JSON data against a correct schema", () => {
const schema = JSON.stringify({
type: "object",
properties: {
name: { type: "string" },
age: { type: "number" },
},
required: ["name", "age"],
});

const jsonData = JSON.stringify({ name: "John", age: 30 });

const result = validateJsonSchema(schema, jsonData);
expect(result.valid).toBe(true);
expect(result.errors).toEqual([]);
});

test("should return errors for invalid JSON data", () => {
const schema = JSON.stringify({
type: "object",
properties: {
name: { type: "string" },
age: { type: "number" },
},
required: ["name", "age"],
});

const jsonData = JSON.stringify({ name: "John" }); // Missing age

const result = validateJsonSchema(schema, jsonData);
expect(result.valid).toBe(false);
expect(result.errors).toEqual(
expect.arrayContaining([
expect.stringContaining("should have required property 'age'"),
])
);
});

test("should return errors for invalid schema", () => {
const schema = "{ invalid JSON schema";
const jsonData = JSON.stringify({ name: "John", age: 30 });

const result = validateJsonSchema(schema, jsonData);
expect(result.valid).toBe(false);
expect(result.errors).toEqual(
expect.arrayContaining([
expect.stringContaining(
"Expected property name or '}' in JSON at position"
),
])
);
});

test("should return errors for invalid JSON data", () => {
const schema = JSON.stringify({
type: "object",
properties: {
name: { type: "string" },
age: { type: "number" },
},
required: ["name", "age"],
});

const jsonData = "{ invalid JSON data";

const result = validateJsonSchema(schema, jsonData);
expect(result.valid).toBe(false);
expect(result.errors).toEqual(
expect.arrayContaining([
expect.stringContaining(
"Expected property name or '}' in JSON at position"
),
])
);
});
});
36 changes: 36 additions & 0 deletions components/utils/json-schema-validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Ajv from "ajv";

/**
* Validates JSON data against a JSON schema.
*
* @param schema - A JSON schema as a string.
* @param jsonData - JSON data as a string.
* @returns An object with `valid` (boolean) and `errors` (array of error messages) properties.
*/
export const validateJsonSchema = (
schema: string,
jsonData: string
): { valid: boolean; errors: string[] } => {
try {
const ajv = new Ajv();
const schemaObj = JSON.parse(schema);
const dataObj = JSON.parse(jsonData);

const validate = ajv.compile(schemaObj);
const valid = validate(dataObj);

if (valid) {
return { valid: true, errors: [] };
} else {
const errors = validate.errors
? validate.errors.map((error) => `${error.dataPath} ${error.message}`)
: [];
return { valid: false, errors };
}
} catch (error) {
if (error instanceof Error) {
return { valid: false, errors: [error.message] };
}
throw error;
}
};
6 changes: 6 additions & 0 deletions components/utils/tools-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,10 @@ export const tools = [
"Test and debug your regular expressions in real-time. Provides quick feedback on pattern matching for strings.",
link: "/utilities/regex-tester",
},
{
title: "JSON Schema Validator",
description:
"Validate and troubleshoot your JSON data structures in real-time. Ensure your JSON complies with the defined schema for accurate data handling.",
link: "/utilities/json-schema-validator",
},
];
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.0",
"avj": "^0.0.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
Expand Down
122 changes: 122 additions & 0 deletions pages/utilities/json-schema-validator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { useState, useEffect, useCallback } from "react";
import { Textarea } from "@/components/ds/TextareaComponent";
import PageHeader from "@/components/PageHeader";
import { Card } from "@/components/ds/CardComponent";
import { Label } from "@/components/ds/LabelComponent";
import Header from "@/components/Header";
import { CMDK } from "@/components/CMDK";
import Meta from "@/components/Meta";
import Ajv from "ajv";
import CallToActionGrid from "@/components/CallToActionGrid";

export default function JsonSchemaValidator() {
const [schema, setSchema] = useState("");
const [jsonData, setJsonData] = useState("");
const [result, setResult] = useState<string | null>("");
const [resultColor, setResultColor] = useState<string>("");

const handleValidation = useCallback(() => {
try {
const ajv = new Ajv();
const validate = ajv.compile(JSON.parse(schema));
const valid = validate(JSON.parse(jsonData));

if (valid) {
setResult("JSON data is valid against the schema");
setResultColor("bg-green-100 text-green-800"); // Green color for success
} else {
setResult(
`JSON data is invalid:\n${validate.errors
?.map((error) => `${error.dataPath} ${error.message}`)
.join("\n")}`
);
setResultColor("bg-red-100 text-red-800"); // Red color for failure
}
} catch (error) {
if (error instanceof Error) {
setResult(`Error: ${error.message}`);
setResultColor("bg-red-100 text-red-800"); // Red color for error
}
}
}, [schema, jsonData]);

useEffect(() => {
if (schema && jsonData) {
handleValidation();
} else {
setResult(""); // Keep the result box empty if fields are not filled
setResultColor(""); // No color if fields are empty
}
}, [schema, jsonData, handleValidation]);

const handleReset = () => {
setSchema("");
setJsonData("");
setResult(""); // Clear the result
setResultColor(""); // Clear the result color
};

return (
<main>
<Meta
title="JSON Schema Validator by Jam.dev | Free, Open Source & Ad-free"
description="Validate your JSON data against a schema with Jam's free online JSON Schema Validator."
/>
<Header />
<CMDK />

<section className="container max-w-2xl mb-12">
<PageHeader
title="JSON Schema Validator"
description="Fast, free, open source, ad-free tools."
/>
</section>

<section className="container max-w-2xl mb-6">
<Card className="flex flex-col p-6 hover:shadow-none shadow-none rounded-xl">
<div>
<Label>JSON Schema</Label>
<Textarea
rows={6}
placeholder="Enter JSON schema here"
onChange={(event) => setSchema(event.target.value)}
className="mb-6"
value={schema}
/>

<Label>JSON Data</Label>
<div>
<Textarea
rows={6}
placeholder="Enter JSON data here"
onChange={(event) => setJsonData(event.target.value)}
className="mb-6"
value={jsonData}
/>
</div>

<div>
<Label>Result</Label>
<div
className={`w-full rounded-lg border border-input px-3 py-2 text-sm ring-offset-background ${resultColor}`}
>
<div>{result}</div>
</div>
</div>

<div className="mt-4">
<button
onClick={handleReset}
className="px-4 py-2 bg-gray-500 text-white rounded-lg hover:bg-gray-600"
>
Reset
</button>
</div>
</div>
</Card>
</section>

<CallToActionGrid />
</main>
);
}