mirror of
https://github.com/iib0011/omni-tools.git
synced 2025-09-18 13:39:31 +02:00
feat: tsv to json
This commit is contained in:
15
.idea/workspace.xml
generated
15
.idea/workspace.xml
generated
@@ -5,11 +5,12 @@
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="feat: qr code generation init">
|
||||
<change afterPath="$PROJECT_DIR$/src/pages/tools/json/tsv-to-json/index.tsx" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/pages/tools/json/tsv-to-json/meta.ts" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/pages/tools/json/tsv-to-json/service.ts" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/pages/tools/json/tsv-to-json/types.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/package-lock.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/package.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/options/ColorSelector.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/options/ColorSelector.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/generic/qr-code/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/qr-code/index.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/pages/tools/json/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/json/index.ts" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
@@ -227,10 +228,10 @@
|
||||
"Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp.executor": "Run",
|
||||
"Vitest.replaceText function.executor": "Run",
|
||||
"Vitest.timeBetweenDates.executor": "Run",
|
||||
"git-widget-placeholder": "qr-code",
|
||||
"git-widget-placeholder": "main",
|
||||
"ignore.virus.scanning.warn.message": "true",
|
||||
"kotlin-language-version-configured": "true",
|
||||
"last_opened_file_path": "C:/Users/Ibrahima/IdeaProjects/omni-tools/src",
|
||||
"last_opened_file_path": "C:/Users/Ibrahima/IdeaProjects/omni-tools/src/pages/tools/json",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
@@ -264,11 +265,11 @@
|
||||
</component>
|
||||
<component name="RecentsManager">
|
||||
<key name="CopyFile.RECENT_KEYS">
|
||||
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\pages\tools\json" />
|
||||
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src" />
|
||||
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\@types" />
|
||||
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\public\assets" />
|
||||
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\components\input" />
|
||||
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\.husky" />
|
||||
</key>
|
||||
<key name="MoveFile.RECENT_KEYS">
|
||||
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\lib\ghostscript" />
|
||||
|
@@ -4,6 +4,7 @@ import { tool as jsonStringify } from './stringify/meta';
|
||||
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';
|
||||
|
||||
export const jsonTools = [
|
||||
validateJson,
|
||||
@@ -11,5 +12,6 @@ export const jsonTools = [
|
||||
jsonMinify,
|
||||
jsonStringify,
|
||||
jsonToXml,
|
||||
escapeJson
|
||||
escapeJson,
|
||||
tsvToJson
|
||||
];
|
||||
|
227
src/pages/tools/json/tsv-to-json/index.tsx
Normal file
227
src/pages/tools/json/tsv-to-json/index.tsx
Normal file
@@ -0,0 +1,227 @@
|
||||
import React, { useState } from 'react';
|
||||
import ToolContent from '@components/ToolContent';
|
||||
import ToolTextInput from '@components/input/ToolTextInput';
|
||||
import ToolTextResult from '@components/result/ToolTextResult';
|
||||
import { convertTsvToJson } from './service';
|
||||
import { CardExampleType } from '@components/examples/ToolExamples';
|
||||
import { GetGroupsType } from '@components/options/ToolOptions';
|
||||
import { ToolComponentProps } from '@tools/defineTool';
|
||||
import { Box } from '@mui/material';
|
||||
import { updateNumberField } from '@utils/string';
|
||||
import SimpleRadio from '@components/options/SimpleRadio';
|
||||
import CheckboxWithDesc from '@components/options/CheckboxWithDesc';
|
||||
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
|
||||
import { InitialValuesType } from './types';
|
||||
|
||||
const initialValues: InitialValuesType = {
|
||||
delimiter: '\t',
|
||||
quote: '"',
|
||||
comment: '#',
|
||||
useHeaders: true,
|
||||
skipEmptyLines: true,
|
||||
dynamicTypes: true,
|
||||
indentationType: 'space',
|
||||
spacesCount: 2
|
||||
};
|
||||
|
||||
const exampleCards: CardExampleType<InitialValuesType>[] = [
|
||||
{
|
||||
title: 'Basic TSV to JSON Array',
|
||||
description:
|
||||
'Convert a simple TSV file into a JSON array structure by using spaces as formatting indentation.',
|
||||
sampleText: `name age city
|
||||
John 30 New York
|
||||
Alice 25 London`,
|
||||
sampleResult: `[
|
||||
{
|
||||
"name": "John",
|
||||
"age": 30,
|
||||
"city": "New York"
|
||||
},
|
||||
{
|
||||
"name": "Alice",
|
||||
"age": 25,
|
||||
"city": "London"
|
||||
}
|
||||
]`,
|
||||
sampleOptions: {
|
||||
...initialValues,
|
||||
useHeaders: true,
|
||||
dynamicTypes: true
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Turn TSV to JSON without Headers',
|
||||
description: 'Convert a TSV file in minified JSON file.',
|
||||
sampleText: `Square Triangle Circle
|
||||
Cube Cone Sphere
|
||||
#Oval`,
|
||||
sampleResult: `[["Square","Triangle","Circle"],["Cube","Cone","Sphere"]]`,
|
||||
sampleOptions: {
|
||||
...initialValues,
|
||||
useHeaders: false,
|
||||
indentationType: 'none'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Transform TSV to JSON with Headers',
|
||||
description: 'Convert a TSV file with headers into a JSON file.',
|
||||
sampleText: `item material quantity
|
||||
|
||||
|
||||
Hat Wool 3
|
||||
Gloves Leather 5
|
||||
Candle Wax 4
|
||||
Vase Glass 2
|
||||
|
||||
Sculpture Bronze 1
|
||||
Table Wood 1
|
||||
|
||||
Bookshelf Wood 2`,
|
||||
sampleResult: `[
|
||||
{
|
||||
"item": "Hat",
|
||||
"material": "Wool",
|
||||
"quantity": 3
|
||||
},
|
||||
{
|
||||
"item": "Gloves",
|
||||
"material": "Leather",
|
||||
"quantity": 5
|
||||
},
|
||||
{
|
||||
"item": "Candle",
|
||||
"material": "Wax",
|
||||
"quantity": 4
|
||||
},
|
||||
{
|
||||
"item": "Vase",
|
||||
"material": "Glass",
|
||||
"quantity": 2
|
||||
},
|
||||
{
|
||||
"item": "Sculpture",
|
||||
"material": "Bronze",
|
||||
"quantity": 1
|
||||
},
|
||||
{
|
||||
"item": "Table",
|
||||
"material": "Wood",
|
||||
"quantity": 1
|
||||
},
|
||||
{
|
||||
"item": "Bookshelf",
|
||||
"material": "Wood",
|
||||
"quantity": 2
|
||||
}
|
||||
]`,
|
||||
sampleOptions: {
|
||||
...initialValues
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
export default function TsvToJson({
|
||||
title,
|
||||
longDescription
|
||||
}: ToolComponentProps) {
|
||||
const [input, setInput] = useState<string>('');
|
||||
const [result, setResult] = useState<string>('');
|
||||
|
||||
const compute = (values: InitialValuesType, input: string) => {
|
||||
setResult(convertTsvToJson(input, values));
|
||||
};
|
||||
|
||||
const getGroups: GetGroupsType<InitialValuesType> | null = ({
|
||||
values,
|
||||
updateField
|
||||
}) => [
|
||||
{
|
||||
title: 'Input CSV Format',
|
||||
component: (
|
||||
<Box>
|
||||
<TextFieldWithDesc
|
||||
description="Character used to qutoe tsv values"
|
||||
onOwnChange={(val) => updateField('quote', val)}
|
||||
value={values.quote}
|
||||
/>
|
||||
<TextFieldWithDesc
|
||||
description="Symbol use to mark comments in the TSV"
|
||||
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.dynamicTypes}
|
||||
onChange={(value) => updateField('dynamicTypes', value)}
|
||||
title="Dynamic Types"
|
||||
description="Convert numbers and booleans to their proper types"
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Output Formatting',
|
||||
component: (
|
||||
<Box>
|
||||
<SimpleRadio
|
||||
onClick={() => updateField('indentationType', 'space')}
|
||||
checked={values.indentationType === 'space'}
|
||||
title={'Use Spaces for indentation'}
|
||||
/>
|
||||
{values.indentationType === 'space' && (
|
||||
<TextFieldWithDesc
|
||||
description="Number of spaces for indentation"
|
||||
value={values.spacesCount}
|
||||
onOwnChange={(val) =>
|
||||
updateNumberField(val, 'spacesCount', updateField)
|
||||
}
|
||||
type="number"
|
||||
/>
|
||||
)}
|
||||
<SimpleRadio
|
||||
onClick={() => updateField('indentationType', 'tab')}
|
||||
checked={values.indentationType === 'tab'}
|
||||
title={'Use Tabs for indentation'}
|
||||
/>
|
||||
<SimpleRadio
|
||||
onClick={() => updateField('indentationType', 'none')}
|
||||
checked={values.indentationType === 'none'}
|
||||
title={'Minify JSON'}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<ToolContent
|
||||
title={title}
|
||||
input={input}
|
||||
setInput={setInput}
|
||||
initialValues={initialValues}
|
||||
compute={compute}
|
||||
exampleCards={exampleCards}
|
||||
getGroups={getGroups}
|
||||
inputComponent={
|
||||
<ToolTextInput title="Input TSV" value={input} onChange={setInput} />
|
||||
}
|
||||
resultComponent={
|
||||
<ToolTextResult title="Output JSON" value={result} extension={'json'} />
|
||||
}
|
||||
toolInfo={{ title: `What is a ${title}?`, description: longDescription }}
|
||||
/>
|
||||
);
|
||||
}
|
15
src/pages/tools/json/tsv-to-json/meta.ts
Normal file
15
src/pages/tools/json/tsv-to-json/meta.ts
Normal file
@@ -0,0 +1,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'],
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
95
src/pages/tools/json/tsv-to-json/service.ts
Normal file
95
src/pages/tools/json/tsv-to-json/service.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { InitialValuesType } from './types';
|
||||
import { beautifyJson } from '../prettify/service';
|
||||
import { minifyJson } from '../minify/service';
|
||||
|
||||
export function convertTsvToJson(
|
||||
input: string,
|
||||
options: InitialValuesType
|
||||
): string {
|
||||
if (!input) return '';
|
||||
const lines = input.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 &&
|
||||
(!options.skipEmptyLines ||
|
||||
!containsOnlyCustomCharAndSpaces(trimmedLine, options.delimiter)) &&
|
||||
!trimmedLine.startsWith(options.comment)
|
||||
);
|
||||
});
|
||||
|
||||
if (validLines.length === 0) {
|
||||
return '[]';
|
||||
}
|
||||
|
||||
// Parse headers if enabled
|
||||
if (options.useHeaders) {
|
||||
headers = parseCsvLine(validLines[0], options);
|
||||
validLines.shift();
|
||||
}
|
||||
|
||||
// Parse data lines
|
||||
for (const line of validLines) {
|
||||
const values = parseCsvLine(line, options);
|
||||
|
||||
if (options.useHeaders) {
|
||||
const obj: Record<string, any> = {};
|
||||
headers.forEach((header, i) => {
|
||||
obj[header] = parseValue(values[i], options.dynamicTypes);
|
||||
});
|
||||
result.push(obj);
|
||||
} else {
|
||||
result.push(values.map((v) => parseValue(v, options.dynamicTypes)));
|
||||
}
|
||||
}
|
||||
|
||||
return options.indentationType === 'none'
|
||||
? minifyJson(JSON.stringify(result))
|
||||
: beautifyJson(
|
||||
JSON.stringify(result),
|
||||
options.indentationType,
|
||||
options.spacesCount
|
||||
);
|
||||
}
|
||||
|
||||
const parseCsvLine = (line: string, options: InitialValuesType): 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);
|
||||
}
|
10
src/pages/tools/json/tsv-to-json/types.ts
Normal file
10
src/pages/tools/json/tsv-to-json/types.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export type InitialValuesType = {
|
||||
delimiter: string;
|
||||
quote: string;
|
||||
comment: string;
|
||||
useHeaders: boolean;
|
||||
skipEmptyLines: boolean;
|
||||
dynamicTypes: boolean;
|
||||
indentationType: 'tab' | 'space' | 'none';
|
||||
spacesCount: number;
|
||||
};
|
Reference in New Issue
Block a user