mirror of
https://github.com/iib0011/omni-tools.git
synced 2025-09-21 06:59:33 +02:00
feat: csv to json
This commit is contained in:
210
src/pages/tools/csv/csv-to-json/index.tsx
Normal file
210
src/pages/tools/csv/csv-to-json/index.tsx
Normal file
@@ -0,0 +1,210 @@
|
||||
import React, { useState } from 'react';
|
||||
import ToolContent from '@components/ToolContent';
|
||||
import ToolTextInput from '@components/input/ToolTextInput';
|
||||
import ToolTextResult from '@components/result/ToolTextResult';
|
||||
import { convertCsvToJson } from './service';
|
||||
import { CardExampleType } from '@components/examples/ToolExamples';
|
||||
import { ToolComponentProps } from '@tools/defineTool';
|
||||
import { Box } from '@mui/material';
|
||||
import RadioWithTextField from '@components/options/RadioWithTextField';
|
||||
import SimpleRadio from '@components/options/SimpleRadio';
|
||||
import CheckboxWithDesc from '@components/options/CheckboxWithDesc';
|
||||
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
|
||||
|
||||
type InitialValuesType = {
|
||||
delimiter: string;
|
||||
quote: string;
|
||||
comment: string;
|
||||
useHeaders: boolean;
|
||||
skipEmptyLines: boolean;
|
||||
dynamicTypes: boolean;
|
||||
indentationType: 'tab' | 'space';
|
||||
spacesCount: number;
|
||||
};
|
||||
|
||||
const initialValues: InitialValuesType = {
|
||||
delimiter: ',',
|
||||
quote: '"',
|
||||
comment: '#',
|
||||
useHeaders: true,
|
||||
skipEmptyLines: true,
|
||||
dynamicTypes: true,
|
||||
indentationType: 'space',
|
||||
spacesCount: 2
|
||||
};
|
||||
|
||||
const exampleCards: CardExampleType<InitialValuesType>[] = [
|
||||
{
|
||||
title: 'Basic CSV to JSON Array',
|
||||
description: 'Convert a simple CSV file into a JSON array structure.',
|
||||
sampleText: 'name,age,city\nJohn,30,New York\nAlice,25,London',
|
||||
sampleResult: `[
|
||||
{
|
||||
"name": "John",
|
||||
"age": 30,
|
||||
"city": "New York"
|
||||
},
|
||||
{
|
||||
"name": "Alice",
|
||||
"age": 25,
|
||||
"city": "London"
|
||||
}
|
||||
]`,
|
||||
sampleOptions: {
|
||||
...initialValues,
|
||||
useHeaders: true,
|
||||
dynamicTypes: true
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'CSV with Custom Delimiter',
|
||||
description: 'Convert a CSV file that uses semicolons as separators.',
|
||||
sampleText: 'product;price;quantity\nApple;1.99;50\nBanana;0.99;100',
|
||||
sampleResult: `[
|
||||
{
|
||||
"product": "Apple",
|
||||
"price": 1.99,
|
||||
"quantity": 50
|
||||
},
|
||||
{
|
||||
"product": "Banana",
|
||||
"price": 0.99,
|
||||
"quantity": 100
|
||||
}
|
||||
]`,
|
||||
sampleOptions: {
|
||||
...initialValues,
|
||||
delimiter: ';'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'CSV with Comments and Empty Lines',
|
||||
description: 'Process CSV data while handling comments and empty lines.',
|
||||
sampleText: `# This is a comment
|
||||
id,name,active
|
||||
1,John,true
|
||||
|
||||
# Another comment
|
||||
2,Jane,false
|
||||
|
||||
3,Bob,true`,
|
||||
sampleResult: `[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "John",
|
||||
"active": true
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Jane",
|
||||
"active": false
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Bob",
|
||||
"active": true
|
||||
}
|
||||
]`,
|
||||
sampleOptions: {
|
||||
...initialValues,
|
||||
skipEmptyLines: true,
|
||||
comment: '#'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
export default function CsvToJson({ title }: ToolComponentProps) {
|
||||
const [input, setInput] = useState<string>('');
|
||||
const [result, setResult] = useState<string>('');
|
||||
|
||||
const compute = (values: InitialValuesType, input: string) => {
|
||||
if (input) {
|
||||
try {
|
||||
const jsonResult = convertCsvToJson(input, {
|
||||
delimiter: values.delimiter,
|
||||
quote: values.quote,
|
||||
comment: values.comment,
|
||||
useHeaders: values.useHeaders,
|
||||
skipEmptyLines: values.skipEmptyLines,
|
||||
dynamicTypes: values.dynamicTypes
|
||||
});
|
||||
setResult(jsonResult);
|
||||
} catch (error) {
|
||||
setResult(
|
||||
`Error: ${
|
||||
error instanceof Error ? error.message : 'Invalid CSV format'
|
||||
}`
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ToolContent
|
||||
title={title}
|
||||
input={input}
|
||||
setInput={setInput}
|
||||
initialValues={initialValues}
|
||||
compute={compute}
|
||||
exampleCards={exampleCards}
|
||||
inputComponent={
|
||||
<ToolTextInput title="Input CSV" value={input} onChange={setInput} />
|
||||
}
|
||||
resultComponent={<ToolTextResult title="Output JSON" value={result} />}
|
||||
getGroups={({ values, updateField }) => [
|
||||
{
|
||||
title: 'Input CSV Format',
|
||||
component: (
|
||||
<Box>
|
||||
<TextFieldWithDesc
|
||||
description="Column Separator"
|
||||
value={values.delimiter}
|
||||
onOwnChange={(val) => updateField('delimiter', val)}
|
||||
/>
|
||||
<TextFieldWithDesc
|
||||
description="Field Quote"
|
||||
onOwnChange={(val) => updateField('quote', val)}
|
||||
value={values.quote}
|
||||
/>
|
||||
<TextFieldWithDesc
|
||||
description="Comment Symbol"
|
||||
value={values.comment}
|
||||
onOwnChange={(val) => updateField('comment', val)}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Conversion Options',
|
||||
component: (
|
||||
<Box>
|
||||
<CheckboxWithDesc
|
||||
checked={values.useHeaders}
|
||||
onChange={(value) => updateField('useHeaders', value)}
|
||||
title="Use Headers"
|
||||
description="First row is treated as column headers"
|
||||
/>
|
||||
<CheckboxWithDesc
|
||||
checked={values.skipEmptyLines}
|
||||
onChange={(value) => updateField('skipEmptyLines', value)}
|
||||
title="Skip Empty Lines"
|
||||
description="Don't process empty lines in the CSV"
|
||||
/>
|
||||
<CheckboxWithDesc
|
||||
checked={values.dynamicTypes}
|
||||
onChange={(value) => updateField('dynamicTypes', value)}
|
||||
title="Dynamic Types"
|
||||
description="Convert numbers and booleans to their proper types"
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
]}
|
||||
toolInfo={{
|
||||
title: 'What Is a CSV to JSON Converter?',
|
||||
description:
|
||||
'This tool transforms Comma Separated Values (CSV) files to JavaScript Object Notation (JSON) data structures. It supports various CSV formats with customizable delimiters, quote characters, and comment symbols. The converter can treat the first row as headers, skip empty lines, and automatically detect data types like numbers and booleans. The resulting JSON can be used for data migration, backups, or as input for other applications.'
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
13
src/pages/tools/csv/csv-to-json/meta.ts
Normal file
13
src/pages/tools/csv/csv-to-json/meta.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { defineTool } from '@tools/defineTool';
|
||||
import { lazy } from 'react';
|
||||
|
||||
export const tool = defineTool('csv', {
|
||||
name: 'Convert CSV to JSON',
|
||||
path: 'csv-to-json',
|
||||
icon: 'lets-icons:json-light',
|
||||
description:
|
||||
'Convert CSV files to JSON format with customizable options for delimiters, quotes, and output formatting. Support for headers, comments, and dynamic type conversion.',
|
||||
shortDescription: 'Convert CSV data to JSON format',
|
||||
keywords: ['csv', 'json', 'convert', 'transform', 'parse'],
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
103
src/pages/tools/csv/csv-to-json/service.ts
Normal file
103
src/pages/tools/csv/csv-to-json/service.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
type CsvToJsonOptions = {
|
||||
delimiter: string;
|
||||
quote: string;
|
||||
comment: string;
|
||||
useHeaders: boolean;
|
||||
skipEmptyLines: boolean;
|
||||
dynamicTypes: boolean;
|
||||
};
|
||||
|
||||
const defaultOptions: CsvToJsonOptions = {
|
||||
delimiter: ',',
|
||||
quote: '"',
|
||||
comment: '#',
|
||||
useHeaders: true,
|
||||
skipEmptyLines: true,
|
||||
dynamicTypes: true
|
||||
};
|
||||
|
||||
export const convertCsvToJson = (
|
||||
csv: string,
|
||||
options: Partial<CsvToJsonOptions> = {}
|
||||
): string => {
|
||||
const opts = { ...defaultOptions, ...options };
|
||||
const lines = csv.split('\n');
|
||||
const result: any[] = [];
|
||||
let headers: string[] = [];
|
||||
|
||||
// Filter out comments and empty lines
|
||||
const validLines = lines.filter((line) => {
|
||||
const trimmedLine = line.trim();
|
||||
return (
|
||||
trimmedLine &&
|
||||
(!opts.skipEmptyLines ||
|
||||
!containsOnlyCustomCharAndSpaces(trimmedLine, opts.delimiter)) &&
|
||||
!trimmedLine.startsWith(opts.comment)
|
||||
);
|
||||
});
|
||||
|
||||
if (validLines.length === 0) {
|
||||
return '[]';
|
||||
}
|
||||
|
||||
// Parse headers if enabled
|
||||
if (opts.useHeaders) {
|
||||
headers = parseCsvLine(validLines[0], opts);
|
||||
validLines.shift();
|
||||
}
|
||||
|
||||
// Parse data lines
|
||||
for (const line of validLines) {
|
||||
const values = parseCsvLine(line, opts);
|
||||
|
||||
if (opts.useHeaders) {
|
||||
const obj: Record<string, any> = {};
|
||||
headers.forEach((header, i) => {
|
||||
obj[header] = parseValue(values[i], opts.dynamicTypes);
|
||||
});
|
||||
result.push(obj);
|
||||
} else {
|
||||
result.push(values.map((v) => parseValue(v, opts.dynamicTypes)));
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(result, null, 2);
|
||||
};
|
||||
|
||||
const parseCsvLine = (line: string, options: CsvToJsonOptions): string[] => {
|
||||
const values: string[] = [];
|
||||
let currentValue = '';
|
||||
let inQuotes = false;
|
||||
|
||||
for (let i = 0; i < line.length; i++) {
|
||||
const char = line[i];
|
||||
|
||||
if (char === options.quote) {
|
||||
inQuotes = !inQuotes;
|
||||
} else if (char === options.delimiter && !inQuotes) {
|
||||
values.push(currentValue.trim());
|
||||
currentValue = '';
|
||||
} else {
|
||||
currentValue += char;
|
||||
}
|
||||
}
|
||||
|
||||
values.push(currentValue.trim());
|
||||
return values;
|
||||
};
|
||||
|
||||
const parseValue = (value: string, dynamicTypes: boolean): any => {
|
||||
if (!dynamicTypes) return value;
|
||||
|
||||
if (value.toLowerCase() === 'true') return true;
|
||||
if (value.toLowerCase() === 'false') return false;
|
||||
if (value === 'null') return null;
|
||||
if (!isNaN(Number(value))) return Number(value);
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
function containsOnlyCustomCharAndSpaces(str: string, customChar: string) {
|
||||
const regex = new RegExp(`^[${customChar}\\s]*$`);
|
||||
return regex.test(str);
|
||||
}
|
3
src/pages/tools/csv/index.ts
Normal file
3
src/pages/tools/csv/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { tool as csvToJson } from './csv-to-json/meta';
|
||||
|
||||
export const csvTools = [csvToJson];
|
@@ -18,7 +18,8 @@ export type ToolCategory =
|
||||
| 'number'
|
||||
| 'gif'
|
||||
| 'list'
|
||||
| 'json';
|
||||
| 'json'
|
||||
| 'csv';
|
||||
|
||||
export interface DefinedTool {
|
||||
type: ToolCategory;
|
||||
|
@@ -7,6 +7,7 @@ import { videoTools } from '../pages/tools/video';
|
||||
import { listTools } from '../pages/tools/list';
|
||||
import { Entries } from 'type-fest';
|
||||
import { jsonTools } from '../pages/tools/json';
|
||||
import { csvTools } from '../pages/tools/csv';
|
||||
import { IconifyIcon } from '@iconify/react';
|
||||
|
||||
export const tools: DefinedTool[] = [
|
||||
@@ -14,6 +15,7 @@ export const tools: DefinedTool[] = [
|
||||
...stringTools,
|
||||
...jsonTools,
|
||||
...listTools,
|
||||
...csvTools,
|
||||
...videoTools,
|
||||
...numberTools
|
||||
];
|
||||
@@ -59,6 +61,12 @@ const categoriesConfig: {
|
||||
icon: 'lets-icons:json-light',
|
||||
value:
|
||||
'Tools for working with JSON data structures – prettify and minify JSON objects, flatten JSON arrays, stringify JSON values, analyze data, and much more'
|
||||
},
|
||||
{
|
||||
type: 'csv',
|
||||
icon: 'material-symbols-light:csv-outline',
|
||||
value:
|
||||
'Tools for working with CSV files - convert CSV to different formats, manipulate CSV data, validate CSV structure, and process CSV files efficiently.'
|
||||
}
|
||||
];
|
||||
export const filterTools = (
|
||||
|
Reference in New Issue
Block a user