Merge branch 'main' into tools-filtering

This commit is contained in:
AshAnand34
2025-07-18 14:45:15 -07:00
336 changed files with 21767 additions and 2122 deletions

View File

@@ -2,14 +2,15 @@ import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
export const tool = defineTool('json', {
name: 'Escape JSON',
path: 'escape-json',
icon: 'lets-icons:json-light',
description:
'Free online JSON escaper. Just load your JSON in the input field and it will automatically get escaped. In the tool options, you can optionally enable wrapping the escaped JSON in double quotes to get an escaped JSON string.',
shortDescription: 'Quickly escape special JSON characters.',
longDescription: `This tool converts special characters in JSON files and data structures into their escaped versions. Such special characters are, for example, double quotes, newline characters, backslashes, tabs, and many others. If these characters aren't escaped and appear in a raw JSON string without escaping, they can lead to errors in data parsing. The program turns them into safe versions by adding a backslash (\\) before the character, changing its interpretation. Additionally, you can enable the "Wrap Output in Quotes" checkbox in the options, which adds double quotes around the resulting escaped JSON data. This is useful when the escaped JSON data needs to be used as a string in other data structures or the JavaScript programming language. Json-abulous!`,
keywords: ['escape', 'json'],
userTypes: ['Developers'],
component: lazy(() => import('./index'))
icon: 'material-symbols:code',
keywords: ['json', 'escape', 'characters', 'format'],
component: lazy(() => import('./index')),
i18n: {
name: 'json:escapeJson.title',
description: 'json:escapeJson.description',
shortDescription: 'json:escapeJson.shortDescription',
userTypes: ['Developers']
}
});

View File

@@ -5,6 +5,7 @@ import { tool as validateJson } from './validateJson/meta';
import { tool as jsonToXml } from './json-to-xml/meta';
import { tool as escapeJson } from './escape-json/meta';
import { tool as tsvToJson } from './tsv-to-json/meta';
import { tool as jsonComparison } from './json-comparison/meta';
export const jsonTools = [
validateJson,
@@ -13,5 +14,6 @@ export const jsonTools = [
jsonStringify,
jsonToXml,
escapeJson,
tsvToJson
tsvToJson,
jsonComparison
];

View File

@@ -0,0 +1,89 @@
import { useEffect, useState } from 'react';
import ToolContent from '@components/ToolContent';
import ToolCodeInput from '@components/input/ToolCodeInput';
import ToolTextResult from '@components/result/ToolTextResult';
import { compareJson } from './service';
import { ToolComponentProps } from '@tools/defineTool';
import { Grid } from '@mui/material';
type InitialValuesType = {};
const initialValues: InitialValuesType = {};
export default function JsonComparison({ title }: ToolComponentProps) {
const [input1, setInput1] = useState<string>('');
const [input2, setInput2] = useState<string>('');
const [result, setResult] = useState<string>('');
useEffect(() => {
const compareInputs = () => {
try {
// Only compare if at least one input has content
if (input1.trim() || input2.trim()) {
const differences = compareJson(
input1 || '{}',
input2 || '{}',
'text'
);
setResult(differences);
} else {
setResult('');
}
} catch (error) {
setResult(
`Error: ${
error instanceof Error ? error.message : 'Invalid JSON format'
}`
);
}
};
compareInputs();
}, [input1, input2]);
const handleInput1Change = (value: string | undefined) => {
setInput1(value ?? '');
};
const handleInput2Change = (value: string) => {
setInput2(value);
};
return (
<ToolContent
title={title}
input={input1}
setInput={setInput1}
initialValues={initialValues}
getGroups={null}
compute={() => {}}
inputComponent={
<Grid container spacing={2}>
<Grid item xs={12} md={6} lg={4}>
<ToolCodeInput
title="First JSON"
value={input1}
onChange={handleInput1Change}
language={'json'}
/>
</Grid>
<Grid item xs={12} md={6} lg={4}>
<ToolCodeInput
title="Second JSON"
language={'json'}
value={input2}
onChange={handleInput2Change}
/>
</Grid>
<Grid item xs={12} md={12} lg={4}>
<ToolTextResult
title="Differences"
value={result}
extension={'txt'}
/>
</Grid>
</Grid>
}
/>
);
}

View File

@@ -0,0 +1,15 @@
import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
export const tool = defineTool('json', {
path: 'json-comparison',
icon: 'fluent:branch-compare-24-regular',
keywords: ['json', 'compare', 'diff', 'differences', 'match', 'validation'],
component: lazy(() => import('./index')),
i18n: {
name: 'json:comparison.title',
description: 'json:comparison.description',
shortDescription: 'json:comparison.shortDescription'
}
});

View File

@@ -0,0 +1,64 @@
import { compareJson } from './service';
describe('compareJson', () => {
it('should identify missing properties', () => {
const json1 = '{"name": "John", "age": 30}';
const json2 = '{"name": "John"}';
expect(compareJson(json1, json2, 'text')).toContain(
'age: Missing in second JSON'
);
});
it('should identify value mismatches', () => {
const json1 = '{"name": "John", "age": 30}';
const json2 = '{"name": "John", "age": 25}';
expect(compareJson(json1, json2, 'text')).toContain(
'age: Mismatch: 30 != 25'
);
});
it('should handle nested objects', () => {
const json1 = '{"person": {"name": "John", "age": 30}}';
const json2 = '{"person": {"name": "Jane", "age": 30}}';
expect(compareJson(json1, json2, 'text')).toContain(
'person.name: Mismatch: John != Jane'
);
});
it('should return JSON format when specified', () => {
const json1 = '{"name": "John", "age": 30}';
const json2 = '{"name": "Jane", "age": 25}';
const result = compareJson(json1, json2, 'json');
const parsed = JSON.parse(result);
expect(parsed).toHaveProperty('name');
expect(parsed).toHaveProperty('age');
});
it('should handle arrays', () => {
const json1 = '{"numbers": [1, 2, 3]}';
const json2 = '{"numbers": [1, 2, 4]}';
expect(compareJson(json1, json2, 'text')).toContain(
'numbers.2: Mismatch: 3 != 4'
);
});
it('should return "No differences found" for identical JSONs', () => {
const json1 = '{"name": "John", "age": 30}';
const json2 = '{"name": "John", "age": 30}';
expect(compareJson(json1, json2, 'text')).toBe('No differences found');
});
it('should throw error for invalid JSON', () => {
const json1 = '{"name": "John"';
const json2 = '{"name": "John"}';
expect(() => compareJson(json1, json2, 'text')).toThrow();
});
});

View File

@@ -0,0 +1,139 @@
const fixTrailingCommas = (json: string): string => {
// Replace trailing commas in objects and arrays with proper JSON syntax
return json
.replace(/,\s*([}\]])/g, '$1') // Remove trailing commas in objects and arrays
.replace(/,\s*\n\s*([}\]])/g, '\n$1'); // Also handle when the closing bracket is on a new line
};
const tryParseJSON = (
json: string
): { valid: boolean; data?: any; error?: string } => {
if (!json.trim()) {
return { valid: true, data: {} };
}
try {
// Try to parse after fixing trailing commas
const fixedJson = fixTrailingCommas(json);
const data = JSON.parse(fixedJson);
return { valid: true, data };
} catch (error) {
const errorMessage =
error instanceof SyntaxError ? error.message : 'Invalid JSON format';
// Extract line and column info from the error message if available
const match = errorMessage.match(/at line (\d+) column (\d+)/);
if (match) {
const [, line, column] = match;
return {
valid: false,
error: `${errorMessage}\nLocation: Line ${line}, Column ${column}`
};
}
return {
valid: false,
error: errorMessage
};
}
};
export const compareJson = (
json1: string,
json2: string,
format: 'text' | 'json'
): string => {
// Handle empty inputs
if (!json1.trim() && !json2.trim()) return '';
// Parse both JSON inputs
const parsed1 = tryParseJSON(json1);
const parsed2 = tryParseJSON(json2);
// Handle parsing errors
if (!parsed1.valid || !parsed2.valid) {
const errors = [];
if (!parsed1.valid) {
errors.push(`First JSON: ${parsed1.error}`);
}
if (!parsed2.valid) {
errors.push(`Second JSON: ${parsed2.error}`);
}
throw new Error(errors.join('\n\n'));
}
// Compare the valid JSON objects
if (format === 'json') {
const diffs = findDifferencesJSON(parsed1.data, parsed2.data);
return JSON.stringify(diffs);
} else {
const differences = findDifferencesText(parsed1.data, parsed2.data);
if (differences.length === 0) {
return 'No differences found';
}
return differences.join('\n');
}
};
const findDifferencesText = (
obj1: any,
obj2: any,
path: string[] = []
): string[] => {
const differences: string[] = [];
const processPath = (p: string[]): string =>
p.length ? p.join('.') : 'root';
// Compare all keys in obj1
for (const key in obj1) {
const currentPath = [...path, key];
if (!(key in obj2)) {
differences.push(`${processPath(currentPath)}: Missing in second JSON`);
continue;
}
const value1 = obj1[key];
const value2 = obj2[key];
if (
typeof value1 === 'object' &&
value1 !== null &&
typeof value2 === 'object' &&
value2 !== null
) {
differences.push(...findDifferencesText(value1, value2, currentPath));
} else if (value1 !== value2) {
differences.push(
`${processPath(currentPath)}: Mismatch: ${value1} != ${value2}`
);
}
}
// Check for keys in obj2 that don't exist in obj1
for (const key in obj2) {
if (!(key in obj1)) {
const currentPath = [...path, key];
differences.push(`${processPath(currentPath)}: Missing in first JSON`);
}
}
return differences;
};
const findDifferencesJSON = (obj1: any, obj2: any): Record<string, string> => {
const result: Record<string, string> = {};
// Compare all properties
const allKeys = new Set([...Object.keys(obj1), ...Object.keys(obj2)]);
for (const key of allKeys) {
if (!(key in obj1)) {
result[key] = 'Missing in first JSON';
} else if (!(key in obj2)) {
result[key] = 'Missing in second JSON';
} else if (obj1[key] !== obj2[key]) {
result[key] = `Mismatch: ${obj1[key]} != ${obj2[key]}`;
}
}
return result;
};

View File

@@ -2,13 +2,15 @@ import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
export const tool = defineTool('json', {
name: 'Convert JSON to XML',
path: 'json-to-xml',
icon: 'mdi-light:xml',
description:
'Convert JSON data structures to XML format with customizable options for element naming, attributes, and output formatting.',
shortDescription: 'Convert JSON data to XML format.',
keywords: ['json', 'xml', 'convert', 'transform', 'parse'],
userTypes: ['Developers'],
component: lazy(() => import('./index'))
icon: 'material-symbols:code',
keywords: ['json', 'xml', 'convert', 'transform'],
component: lazy(() => import('./index')),
i18n: {
name: 'json:jsonToXml.title',
description: 'json:jsonToXml.description',
shortDescription: 'json:jsonToXml.shortDescription',
userTypes: ['Developers']
}
});

View File

@@ -5,6 +5,7 @@ import ToolTextResult from '@components/result/ToolTextResult';
import { minifyJson } from './service';
import { CardExampleType } from '@components/examples/ToolExamples';
import { ToolComponentProps } from '@tools/defineTool';
import { useTranslation } from 'react-i18next';
type InitialValuesType = Record<string, never>;
@@ -47,6 +48,7 @@ const exampleCards: CardExampleType<InitialValuesType>[] = [
];
export default function MinifyJson({ title }: ToolComponentProps) {
const { t } = useTranslation('json');
const [input, setInput] = useState<string>('');
const [result, setResult] = useState<string>('');
@@ -58,11 +60,15 @@ export default function MinifyJson({ title }: ToolComponentProps) {
<ToolContent
title={title}
inputComponent={
<ToolTextInput title="Input JSON" value={input} onChange={setInput} />
<ToolTextInput
title={t('minify.inputTitle')}
value={input}
onChange={setInput}
/>
}
resultComponent={
<ToolTextResult
title="Minified JSON"
title={t('minify.resultTitle')}
value={result}
extension={'json'}
/>
@@ -70,9 +76,8 @@ export default function MinifyJson({ title }: ToolComponentProps) {
initialValues={initialValues}
getGroups={null}
toolInfo={{
title: 'What Is JSON Minification?',
description:
"JSON minification is the process of removing all unnecessary whitespace characters from JSON data while maintaining its validity. This includes removing spaces, newlines, and indentation that aren't required for the JSON to be parsed correctly. Minification reduces the size of JSON data, making it more efficient for storage and transmission while keeping the exact same data structure and values."
title: t('minify.toolInfo.title'),
description: t('minify.toolInfo.description')
}}
exampleCards={exampleCards}
input={input}

View File

@@ -2,13 +2,15 @@ import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
export const tool = defineTool('json', {
name: 'Minify JSON',
path: 'minify',
icon: 'lets-icons:json-light',
description:
'Minify your JSON by removing all unnecessary whitespace and formatting. This tool compresses JSON data to its smallest possible size while maintaining valid JSON structure.',
shortDescription: 'Quickly compress JSON file.',
keywords: ['minify', 'compress', 'minimize', 'json', 'compact'],
userTypes: ['Developers'],
component: lazy(() => import('./index'))
icon: 'material-symbols:code',
keywords: ['json', 'minify', 'compress', 'whitespace'],
component: lazy(() => import('./index')),
i18n: {
name: 'json:minify.title',
description: 'json:minify.description',
shortDescription: 'json:minify.shortDescription',
userTypes: ['Developers']
}
});

View File

@@ -14,6 +14,7 @@ import RadioWithTextField from '@components/options/RadioWithTextField';
import SimpleRadio from '@components/options/SimpleRadio';
import { isNumber, updateNumberField } from '../../../../utils/string';
import ToolContent from '@components/ToolContent';
import { useTranslation } from 'react-i18next';
type InitialValuesType = {
indentationType: 'tab' | 'space';
@@ -115,6 +116,7 @@ const exampleCards: CardExampleType<InitialValuesType>[] = [
];
export default function PrettifyJson({ title }: ToolComponentProps) {
const { t } = useTranslation('json');
const [input, setInput] = useState<string>('');
const [result, setResult] = useState<string>('');
@@ -128,11 +130,15 @@ export default function PrettifyJson({ title }: ToolComponentProps) {
title={title}
input={input}
inputComponent={
<ToolTextInput title={'Input JSON'} value={input} onChange={setInput} />
<ToolTextInput
title={t('prettify.inputTitle')}
value={input}
onChange={setInput}
/>
}
resultComponent={
<ToolTextResult
title={'Pretty JSON'}
title={t('prettify.resultTitle')}
value={result}
extension={'json'}
/>
@@ -140,14 +146,14 @@ export default function PrettifyJson({ title }: ToolComponentProps) {
initialValues={initialValues}
getGroups={({ values, updateField }) => [
{
title: 'Indentation',
title: t('prettify.indentation'),
component: (
<Box>
<RadioWithTextField
checked={values.indentationType === 'space'}
title={'Use Spaces'}
title={t('prettify.useSpaces')}
fieldName={'indentationType'}
description={'Indent output with spaces'}
description={t('prettify.useSpacesDescription')}
value={values.spacesCount.toString()}
onRadioClick={() => updateField('indentationType', 'space')}
onTextChange={(val) =>
@@ -157,8 +163,8 @@ export default function PrettifyJson({ title }: ToolComponentProps) {
<SimpleRadio
onClick={() => updateField('indentationType', 'tab')}
checked={values.indentationType === 'tab'}
description={'Indent output with tabs.'}
title={'Use Tabs'}
description={t('prettify.useTabsDescription')}
title={t('prettify.useTabs')}
/>
</Box>
)
@@ -168,9 +174,8 @@ export default function PrettifyJson({ title }: ToolComponentProps) {
setInput={setInput}
exampleCards={exampleCards}
toolInfo={{
title: 'What Is a JSON Prettifier?',
description:
'This tool adds consistent formatting to the data in JavaScript Object Notation (JSON) format. This transformation makes the JSON code more readable, making it easier to understand and edit. The program parses the JSON data structure into tokens and then reformats them by adding indentation and line breaks. If the data is hierarchial, then it adds indentation at the beginning of lines to visually show the depth of the JSON and adds newlines to break long single-line JSON arrays into multiple shorter, more readable ones. Additionally, this utility can remove unnecessary spaces and tabs from your JSON code (especially leading and trailing whitespaces), making it more compact. You can choose the line indentation method in the options: indent with spaces or indent with tabs. When using spaces, you can also specify how many spaces to use for each indentation level (usually 2 or 4 spaces). '
title: t('prettify.toolInfo.title'),
description: t('prettify.toolInfo.description')
}}
/>
);

View File

@@ -2,13 +2,15 @@ import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
export const tool = defineTool('json', {
name: 'Prettify JSON',
path: 'prettify',
icon: 'lets-icons:json-light',
description:
"Just load your JSON in the input field and it will automatically get prettified. In the tool options, you can choose whether to use spaces or tabs for indentation and if you're using spaces, you can specify the number of spaces to add per indentation level.",
shortDescription: 'Quickly beautify a JSON data structure.',
keywords: ['prettify'],
userTypes: ['Developers'],
component: lazy(() => import('./index'))
icon: 'material-symbols:code',
keywords: ['json', 'prettify', 'format', 'beautify'],
component: lazy(() => import('./index')),
i18n: {
name: 'json:prettify.title',
description: 'json:prettify.description',
shortDescription: 'json:prettify.shortDescription',
userTypes: ['Developers']
}
});

View File

@@ -2,21 +2,15 @@ import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
export const tool = defineTool('json', {
name: 'Stringify JSON',
path: 'stringify',
icon: 'ant-design:field-string-outlined',
description:
'Convert JavaScript objects and arrays into their JSON string representation. Options include custom indentation and HTML character escaping for web-safe JSON strings.',
shortDescription: 'Convert JavaScript objects to JSON strings',
keywords: [
'stringify',
'serialize',
'convert',
'object',
'array',
'json',
'string'
],
userTypes: ['Developers'],
component: lazy(() => import('./index'))
icon: 'material-symbols:code',
keywords: ['json', 'stringify', 'serialize', 'convert'],
component: lazy(() => import('./index')),
i18n: {
name: 'json:stringify.title',
description: 'json:stringify.description',
shortDescription: 'json:stringify.shortDescription',
userTypes: ['Developers']
}
});

View File

@@ -2,15 +2,15 @@ import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
export const tool = defineTool('json', {
name: 'Convert TSV to JSON',
path: 'tsv-to-json',
icon: 'material-symbols:tsv-rounded',
description:
'Convert TSV files to JSON format with customizable options for delimiters, quotes, and output formatting. Support for headers, comments, and dynamic type conversion.',
shortDescription: 'Convert TSV data to JSON format.',
longDescription:
'This tool allows you to convert TSV (Tab-Separated Values) files into JSON format. You can customize the conversion process by specifying delimiters, quote characters, and whether to use headers. It also supports dynamic type conversion for values, handling comments, and skipping empty lines. The output can be formatted with indentation or minified as needed.',
keywords: ['tsv', 'json', 'convert', 'transform', 'parse'],
userTypes: ['Developers'],
component: lazy(() => import('./index'))
icon: 'material-symbols:code',
keywords: ['tsv', 'json', 'convert', 'tabular'],
component: lazy(() => import('./index')),
i18n: {
name: 'json:tsvToJson.title',
description: 'json:tsvToJson.description',
shortDescription: 'json:tsvToJson.shortDescription',
userTypes: ['Developers']
}
});

View File

@@ -5,6 +5,7 @@ import { CardExampleType } from '@components/examples/ToolExamples';
import { validateJson } from './service';
import { ToolComponentProps } from '@tools/defineTool';
import ToolContent from '@components/ToolContent';
import { useTranslation } from 'react-i18next';
const exampleCards: CardExampleType<{}>[] = [
{
@@ -46,6 +47,7 @@ const exampleCards: CardExampleType<{}>[] = [
];
export default function ValidateJson({ title }: ToolComponentProps) {
const { t } = useTranslation('json');
const [input, setInput] = useState<string>('');
const [result, setResult] = useState<string>('');
@@ -53,9 +55,9 @@ export default function ValidateJson({ title }: ToolComponentProps) {
const { valid, error } = validateJson(input);
if (valid) {
setResult('✅ Valid JSON');
setResult(t('validateJson.validJson'));
} else {
setResult(`${error}`);
setResult(t('validateJson.invalidJson', { error }));
}
};
@@ -63,25 +65,20 @@ export default function ValidateJson({ title }: ToolComponentProps) {
<ToolContent
title={title}
inputComponent={
<ToolTextInput title="Input JSON" value={input} onChange={setInput} />
<ToolTextInput
title={t('validateJson.inputTitle')}
value={input}
onChange={setInput}
/>
}
resultComponent={
<ToolTextResult title="Validation Result" value={result} />
<ToolTextResult title={t('validateJson.resultTitle')} value={result} />
}
initialValues={{}}
getGroups={null}
toolInfo={{
title: 'What is JSON Validation?',
description: `
JSON (JavaScript Object Notation) is a lightweight data-interchange format.
JSON validation ensures that the structure of the data conforms to the JSON standard.
A valid JSON object must have:
- Property names enclosed in double quotes.
- Properly balanced curly braces {}.
- No trailing commas after the last key-value pair.
- Proper nesting of objects and arrays.
This tool checks the input JSON and provides feedback to help identify and fix common errors.
`
title: t('validateJson.toolInfo.title'),
description: t('validateJson.toolInfo.description')
}}
exampleCards={exampleCards}
input={input}

View File

@@ -2,13 +2,15 @@ import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
export const tool = defineTool('json', {
name: 'Validate JSON',
path: 'validateJson',
icon: 'lets-icons:json-light',
description:
'Validate JSON data for syntax errors and structural correctness. This tool helps ensure your JSON is properly formatted and valid.',
shortDescription: 'Validate JSON syntax and structure',
keywords: ['validate', 'json', 'syntax', 'check', 'verify'],
userTypes: ['Developers'],
component: lazy(() => import('./index'))
icon: 'material-symbols:check-circle',
keywords: ['json', 'validate', 'check', 'syntax', 'errors'],
component: lazy(() => import('./index')),
i18n: {
name: 'json:validateJson.title',
description: 'json:validateJson.description',
shortDescription: 'json:validateJson.shortDescription',
userTypes: ['General Users', 'Students', 'Developers']
}
});