Skip to content

Commit

Permalink
Add json schema validator
Browse files Browse the repository at this point in the history
  • Loading branch information
255kb committed Nov 22, 2024
1 parent c2830e7 commit b7fcc80
Show file tree
Hide file tree
Showing 10 changed files with 336 additions and 33 deletions.
2 changes: 1 addition & 1 deletion components/editors/base-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ const BaseEditor: FunctionComponent<{
</div>
)}
{!error && showValidMsg && (
<div className='bg-success-subtle border-start border-success border-4 p-4 my-4'>
<div className='bg-success-subtle border-start border-success border-4 p-4 mt-4'>
<div>{lang.toUpperCase()} is valid!</div>
</div>
)}
Expand Down
24 changes: 2 additions & 22 deletions components/editors/json-editor.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,7 @@
import { json, jsonParseLinter } from '@codemirror/lang-json';
import { linter } from '@codemirror/lint';
import { Text } from '@uiw/react-codemirror';
import { json } from '@codemirror/lang-json';
import { FunctionComponent } from 'react';
import BaseEditor from './base-editor';

const jsonLinter = linter(jsonParseLinter(), { delay: 100 });

function getErrorPosition(
error: SyntaxError,
doc: Text
): { line?: number; column?: number; position?: number } {
let match;
if ((match = error.message.match(/at position (\d+)/)))
return { position: Math.min(+match[1], doc.length) };
if ((match = error.message.match(/at line (\d+) column (\d+)/)))
return {
position: Math.min(doc.line(+match[1]).from + +match[2] - 1, doc.length)
};

return {
position: 1
};
}
import { getErrorPosition, jsonLinter } from './json-utils';

const JsonEditor: FunctionComponent<{
value: string;
Expand Down
63 changes: 63 additions & 0 deletions components/editors/json-schema-content-editor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { json } from '@codemirror/lang-json';
import { FunctionComponent, useState } from 'react';
import BaseEditor from './base-editor';
import { getErrorPosition, initAjv, jsonLinter } from './json-utils';

/**
* Contains a JSON object to be validated against a JSON schema
*
* @param props
* @returns
*/
const JsonSchemaContentEditor: FunctionComponent<{
value: string;
schema: string;
showValidMsg?: boolean;
showErrors?: boolean;
onValueChange?: (value: string) => void;
}> = function (props) {
const [hideGoToLine, setHideGoToLine] = useState<boolean>(false);
const ajvInstance = initAjv();

return (
<BaseEditor
lang='json'
editorExtensions={[json(), jsonLinter]}
onErrorChange={(currentValue: string, viewUpdate) => {
try {
JSON.parse(currentValue);

const validate = ajvInstance.compile(JSON.parse(props.schema));
const isValid = validate(JSON.parse(currentValue));

if (!isValid) {
setHideGoToLine(true);

return {
message: `Validation errors:\n${validate.errors
.slice(0, 6)
.map(
(error) =>
`${error.instancePath ? error.instancePath.replace(/^\//, '') + ' ' : 'JSON '}${error.message}`
)
.join('\n')}`
};
}

return null;
} catch (error) {
setHideGoToLine(false);

return {
message: `JSON error: ${error.message}`,
...getErrorPosition(error, viewUpdate.state.doc)
};
}
}}
hideGoToLine={hideGoToLine}
{...props}
/>
);
};

export default JsonSchemaContentEditor;
49 changes: 49 additions & 0 deletions components/editors/json-schema-editor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { json } from '@codemirror/lang-json';
import { FunctionComponent } from 'react';
import BaseEditor from './base-editor';
import { getErrorPosition, initAjv, jsonLinter } from './json-utils';

/**
* Contains a JSON schema
*
* @param props
* @returns
*/
const JsonSchemaEditor: FunctionComponent<{
value: string;
showValidMsg?: boolean;
showErrors?: boolean;
onValueChange?: (value: string) => void;
}> = function (props) {
const ajvInstance = initAjv();

return (
<BaseEditor
lang='json'
editorExtensions={[json(), jsonLinter]}
onErrorChange={(currentValue: string, viewUpdate) => {
try {
JSON.parse(currentValue);

try {
ajvInstance.compile(JSON.parse(currentValue));
} catch (error) {
return {
message: `Schema error: ${error.message.replace('strict mode: ', '')}`,
...getErrorPosition(error, viewUpdate.state.doc)
};
}
return null;
} catch (error) {
return {
message: `JSON error: ${error.message}`,
...getErrorPosition(error, viewUpdate.state.doc)
};
}
}}
{...props}
/>
);
};

export default JsonSchemaEditor;
30 changes: 30 additions & 0 deletions components/editors/json-utils.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { jsonParseLinter } from '@codemirror/lang-json';
import { linter } from '@codemirror/lint';
import { Text } from '@uiw/react-codemirror';
import Ajv from 'ajv';
import addFormats from 'ajv-formats';

export const jsonLinter = linter(jsonParseLinter(), { delay: 100 });

export function getErrorPosition(
error: SyntaxError,
doc: Text
): { line?: number; column?: number; position?: number } {
let match;
if ((match = error.message.match(/at position (\d+)/)))
return { position: Math.min(+match[1], doc.length) };
if ((match = error.message.match(/at line (\d+) column (\d+)/)))
return {
position: Math.min(doc.line(+match[1]).from + +match[2] - 1, doc.length)
};

return {
position: 1
};
}

export function initAjv() {
const ajv = new Ajv({ allErrors: true });
addFormats(ajv);
return ajv;
}
34 changes: 24 additions & 10 deletions package-lock.json

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

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
"@tanstack/react-query": "^5.8.3",
"@uiw/codemirror-theme-nord": "^4.21.21",
"@uiw/react-codemirror": "^4.21.21",
"ajv": "^8.17.1",
"ajv-formats": "^3.0.1",
"bootstrap": "^5.3.2",
"date-fns": "^3.6.0",
"eslint-config-next": "^14.0.2",
Expand Down
11 changes: 11 additions & 0 deletions pages/tools/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,17 @@ const tools = [
}
],
imageSrc: '/images/illustrations/uuid-generator.svg'
},
{
title: 'JSON schema validator',
description: 'Validate your JSON data against a JSON schema online',
links: [
{
src: '/tools/json-schema-validator/',
text: 'Generate'
}
],
imageSrc: '/images/illustrations/json-schema-validator.svg'
}
];

Expand Down
Loading

0 comments on commit b7fcc80

Please sign in to comment.