feat(new tool): Implement JSON sorter, addressing issue #941
This commit is contained in:
		
							parent
							
								
									d3b32cc14e
								
							
						
					
					
						commit
						22cdc82e3f
					
				
							
								
								
									
										17
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										17
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -89,7 +89,9 @@ declare module '@vue/runtime-core' { | ||||
|     HttpStatusCodes: typeof import('./src/tools/http-status-codes/http-status-codes.vue')['default'] | ||||
|     IbanValidatorAndParser: typeof import('./src/tools/iban-validator-and-parser/iban-validator-and-parser.vue')['default'] | ||||
|     'IconMdi:brushVariant': typeof import('~icons/mdi/brush-variant')['default'] | ||||
|     'IconMdi:contentCopy': typeof import('~icons/mdi/content-copy')['default'] | ||||
|     'IconMdi:kettleSteamOutline': typeof import('~icons/mdi/kettle-steam-outline')['default'] | ||||
|     IconMdiArrowDown: typeof import('~icons/mdi/arrow-down')['default'] | ||||
|     IconMdiChevronDown: typeof import('~icons/mdi/chevron-down')['default'] | ||||
|     IconMdiChevronRight: typeof import('~icons/mdi/chevron-right')['default'] | ||||
|     IconMdiClose: typeof import('~icons/mdi/close')['default'] | ||||
| @ -108,6 +110,7 @@ declare module '@vue/runtime-core' { | ||||
|     Ipv6UlaGenerator: typeof import('./src/tools/ipv6-ula-generator/ipv6-ula-generator.vue')['default'] | ||||
|     JsonDiff: typeof import('./src/tools/json-diff/json-diff.vue')['default'] | ||||
|     JsonMinify: typeof import('./src/tools/json-minify/json-minify.vue')['default'] | ||||
|     JsonSortMaster: typeof import('./src/tools/json-sort-master/json-sort-master.vue')['default'] | ||||
|     JsonToCsv: typeof import('./src/tools/json-to-csv/json-to-csv.vue')['default'] | ||||
|     JsonToToml: typeof import('./src/tools/json-to-toml/json-to-toml.vue')['default'] | ||||
|     JsonToYaml: typeof import('./src/tools/json-to-yaml-converter/json-to-yaml.vue')['default'] | ||||
| @ -126,25 +129,38 @@ declare module '@vue/runtime-core' { | ||||
|     MenuLayout: typeof import('./src/components/MenuLayout.vue')['default'] | ||||
|     MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default'] | ||||
|     MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default'] | ||||
|     NAlert: typeof import('naive-ui')['NAlert'] | ||||
|     NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default'] | ||||
|     NCheckbox: typeof import('naive-ui')['NCheckbox'] | ||||
|     NCode: typeof import('naive-ui')['NCode'] | ||||
|     NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] | ||||
|     NColorPicker: typeof import('naive-ui')['NColorPicker'] | ||||
|     NConfigProvider: typeof import('naive-ui')['NConfigProvider'] | ||||
|     NDatePicker: typeof import('naive-ui')['NDatePicker'] | ||||
|     NDivider: typeof import('naive-ui')['NDivider'] | ||||
|     NEllipsis: typeof import('naive-ui')['NEllipsis'] | ||||
|     NForm: typeof import('naive-ui')['NForm'] | ||||
|     NFormItem: typeof import('naive-ui')['NFormItem'] | ||||
|     NGi: typeof import('naive-ui')['NGi'] | ||||
|     NGrid: typeof import('naive-ui')['NGrid'] | ||||
|     NH1: typeof import('naive-ui')['NH1'] | ||||
|     NH3: typeof import('naive-ui')['NH3'] | ||||
|     NIcon: typeof import('naive-ui')['NIcon'] | ||||
|     NImage: typeof import('naive-ui')['NImage'] | ||||
|     NInputGroup: typeof import('naive-ui')['NInputGroup'] | ||||
|     NInputGroupLabel: typeof import('naive-ui')['NInputGroupLabel'] | ||||
|     NInputNumber: typeof import('naive-ui')['NInputNumber'] | ||||
|     NLabel: typeof import('naive-ui')['NLabel'] | ||||
|     NLayout: typeof import('naive-ui')['NLayout'] | ||||
|     NLayoutSider: typeof import('naive-ui')['NLayoutSider'] | ||||
|     NMenu: typeof import('naive-ui')['NMenu'] | ||||
|     NScrollbar: typeof import('naive-ui')['NScrollbar'] | ||||
|     NSlider: typeof import('naive-ui')['NSlider'] | ||||
|     NSpin: typeof import('naive-ui')['NSpin'] | ||||
|     NStatistic: typeof import('naive-ui')['NStatistic'] | ||||
|     NSwitch: typeof import('naive-ui')['NSwitch'] | ||||
|     NTable: typeof import('naive-ui')['NTable'] | ||||
|     NTag: typeof import('naive-ui')['NTag'] | ||||
|     NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default'] | ||||
|     OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default'] | ||||
|     PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default'] | ||||
| @ -159,6 +175,7 @@ declare module '@vue/runtime-core' { | ||||
|     RouterLink: typeof import('vue-router')['RouterLink'] | ||||
|     RouterView: typeof import('vue-router')['RouterView'] | ||||
|     RsaKeyPairGenerator: typeof import('./src/tools/rsa-key-pair-generator/rsa-key-pair-generator.vue')['default'] | ||||
|     SafelinkDecoder: typeof import('./src/tools/safelink-decoder/safelink-decoder.vue')['default'] | ||||
|     SlugifyString: typeof import('./src/tools/slugify-string/slugify-string.vue')['default'] | ||||
|     SpanCopyable: typeof import('./src/components/SpanCopyable.vue')['default'] | ||||
|     SqlPrettify: typeof import('./src/tools/sql-prettify/sql-prettify.vue')['default'] | ||||
|  | ||||
| @ -328,6 +328,10 @@ tools: | ||||
|     title: PDF signature checker | ||||
|     description: Verify the signatures of a PDF file. A signed PDF file contains one or more signatures that may be used to determine whether the contents of the file have been altered since the file was signed. | ||||
| 
 | ||||
|   json-sort-master: | ||||
|     title: JSON sort master | ||||
|     description: Sort your JSON by keys and values. | ||||
| 
 | ||||
|   json-minify: | ||||
|     title: JSON minify | ||||
|     description: Minify and compress your JSON by removing unnecessary white spaces. | ||||
|  | ||||
| @ -68,4 +68,8 @@ tools: | ||||
|     math: Math | ||||
|     measurement: Measurement | ||||
|     text: Text | ||||
|     data: Data | ||||
|     data: Data | ||||
| 
 | ||||
|   json-sort-master: | ||||
|     title: Maestro de clasificación JSON | ||||
|     description: Ordena tu JSON por claves y valores. | ||||
| @ -79,3 +79,7 @@ tools: | ||||
|     copied: Le token a été copié | ||||
|     length: Longueur | ||||
|     tokenPlaceholder: Le token... | ||||
| 
 | ||||
|   json-sort-master: | ||||
|     title: Maître de tri JSON | ||||
|     description: Triez votre JSON par clés et valeurs. | ||||
| @ -69,3 +69,7 @@ tools: | ||||
|     measurement: 'Medidas' | ||||
|     text: 'Texto' | ||||
|     data: 'Dados' | ||||
| 
 | ||||
|   json-sort-master: | ||||
|     title: 'Mestre de classificação JSON' | ||||
|     description: 'Ordene seu JSON por chaves e valores.' | ||||
|  | ||||
| @ -69,3 +69,7 @@ tools: | ||||
|     measurement: Вимірювання | ||||
|     text: Текст | ||||
|     data: Дані | ||||
| 
 | ||||
|   json-sort-master: | ||||
|     title: JSON сортувальник | ||||
|     description: Сортуйте ваш JSON за ключами та значеннями. | ||||
|  | ||||
| @ -221,6 +221,10 @@ tools: | ||||
|     title: Định dạng và làm đẹp JSON | ||||
|     description: Định dạng chuỗi JSON của bạn thành một định dạng dễ đọc và thân thiện với con người. | ||||
| 
 | ||||
|   json-sort-master: | ||||
|     title: Sắp xếp JSON | ||||
|     description: Sắp xếp JSON của bạn theo các khóa và giá trị. | ||||
|      | ||||
|   docker-run-to-docker-compose-converter: | ||||
|     title: Chuyển đổi lệnh docker run thành tệp docker-compose | ||||
|     description: Chuyển đổi các lệnh docker run thành tệp docker-compose! | ||||
|  | ||||
| @ -225,6 +225,10 @@ tools: | ||||
|     title: JSON美化和格式化 | ||||
|     description: 将JSON字符串修饰为友好的可读格式。 | ||||
| 
 | ||||
|   json-sort-master: | ||||
|     title: JSON排序大师 | ||||
|     description: 按键和值对您的JSON进行排序。 | ||||
|      | ||||
|   docker-run-to-docker-compose-converter: | ||||
|     title: Docker Run 到 docker-compose 转换器 | ||||
|     description: 将 docker run 命令行转换为 docker-compose 文件! | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| import { tool as base64FileConverter } from './base64-file-converter'; | ||||
| import { tool as base64StringConverter } from './base64-string-converter'; | ||||
| import { tool as basicAuthGenerator } from './basic-auth-generator'; | ||||
| import { tool as jsonSortMaster } from './json-sort-master'; | ||||
| 
 | ||||
| import { tool as asciiTextDrawer } from './ascii-text-drawer'; | ||||
| 
 | ||||
| @ -104,6 +105,7 @@ export const toolsByCategory: ToolCategory[] = [ | ||||
|       yamlToToml, | ||||
|       jsonToYaml, | ||||
|       jsonToToml, | ||||
|       jsonSortMaster, | ||||
|       listConverter, | ||||
|       tomlToJson, | ||||
|       tomlToYaml, | ||||
|  | ||||
							
								
								
									
										13
									
								
								src/tools/json-sort-master/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/tools/json-sort-master/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| import { ArrowsSort } from '@vicons/tabler'; | ||||
| import { defineTool } from '../tool'; | ||||
| import { translate } from '@/plugins/i18n.plugin'; | ||||
| 
 | ||||
| export const tool = defineTool({ | ||||
|   name: translate('tools.json-sort-master.title'), | ||||
|   path: '/json-sort-master', | ||||
|   description: translate('tools.json-sort-master.description'), | ||||
|   keywords: ['json', 'sort'], | ||||
|   component: () => import('./json-sort-master.vue'), | ||||
|   icon: ArrowsSort, | ||||
|   createdAt: new Date('2024-03-27'), | ||||
| }); | ||||
							
								
								
									
										80
									
								
								src/tools/json-sort-master/json-sort-master.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src/tools/json-sort-master/json-sort-master.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,80 @@ | ||||
| <script setup lang="ts"> | ||||
| import JSON5 from 'json5'; | ||||
| import { useStorage } from '@vueuse/core'; | ||||
| import { formatJson } from './json.models'; | ||||
| import { withDefaultOnError } from '@/utils/defaults'; | ||||
| import { useValidation } from '@/composable/validation'; | ||||
| import TextareaCopyable from '@/components/TextareaCopyable.vue'; | ||||
| 
 | ||||
| const inputElement = ref<HTMLElement>(); | ||||
| 
 | ||||
| const rawJson = useStorage('json-prettify:raw-json', '{"hello": "world", "foo": "bar"}'); | ||||
| const sortMethod = useStorage('json-prettify:sort-method', 'key_name'); | ||||
| const indentSize = useStorage('json-prettify:indent-size', 3); | ||||
| const keyName = ref(''); | ||||
| const cleanJson = computed(() => withDefaultOnError(() => formatJson({ rawJson, sortMethod, keyName, indentSize }), '')); | ||||
| 
 | ||||
| const rawJsonValidation = useValidation({ | ||||
|   source: rawJson, | ||||
|   rules: [ | ||||
|     { | ||||
|       validator: v => v === '' || JSON5.parse(v), | ||||
|       message: 'Provided JSON is not valid.', | ||||
|     }, | ||||
|   ], | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <div style="flex: 0 0 100%"> | ||||
|     <div style="margin: 0 auto; max-width: 400px" flex justify-center gap-3> | ||||
|       <c-select | ||||
|         v-model:value="sortMethod" mb-4 style="width: 200px" label="Sort Method" :options="[ | ||||
|           { | ||||
|             label: 'Key Name', | ||||
|             value: 'key_name', | ||||
|           }, | ||||
|           { | ||||
|             label: 'Key Value', | ||||
|             value: 'key_val', | ||||
|           }, | ||||
|           { | ||||
|             label: 'Key Name (Descending)', | ||||
|             value: 'key_name_desc', | ||||
|           }, | ||||
|           { | ||||
|             label: 'Key Value (Descending)', | ||||
|             value: 'key_val_desc', | ||||
|           }, | ||||
|         ]" | ||||
|       /> | ||||
| 
 | ||||
|       <c-input-text v-if="!['key_name', 'key_name_desc'].includes(sortMethod)" v-model:value="keyName" label="Key Name:" style="width: 200px" clearable raw-text /> | ||||
|     </div> | ||||
|   </div> | ||||
| 
 | ||||
|   <n-form-item | ||||
|     label="Your raw JSON" :feedback="rawJsonValidation.message" | ||||
|     :validation-status="rawJsonValidation.status" | ||||
|   > | ||||
|     <c-input-text | ||||
|       ref="inputElement" v-model:value="rawJson" placeholder="Paste your raw JSON here..." rows="20" | ||||
|       multiline autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" monospace | ||||
|     /> | ||||
|   </n-form-item> | ||||
|   <n-form-item label="Sorted version of your JSON"> | ||||
|     <TextareaCopyable :value="cleanJson" language="json" :follow-height-of="inputElement" /> | ||||
|   </n-form-item> | ||||
| </template> | ||||
| 
 | ||||
| <style lang="less" scoped> | ||||
| .result-card { | ||||
|   position: relative; | ||||
| 
 | ||||
|   .copy-button { | ||||
|     position: absolute; | ||||
|     top: 10px; | ||||
|     right: 10px; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										74
									
								
								src/tools/json-sort-master/json.models.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								src/tools/json-sort-master/json.models.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,74 @@ | ||||
| import { type MaybeRef, get } from '@vueuse/core'; | ||||
| import JSON5 from 'json5'; | ||||
| 
 | ||||
| export { sortObjectKeys, sortObjectValues, formatJson }; | ||||
| 
 | ||||
| function sortObjectKeys<T>(obj: T, sortMethod: string): T { | ||||
|   if (typeof obj !== 'object' || obj === null) { | ||||
|     return obj; | ||||
|   } | ||||
| 
 | ||||
|   if (Array.isArray(obj)) { | ||||
|     return obj.map(value => sortObjectKeys(value, sortMethod)) as unknown as T; | ||||
|   } | ||||
| 
 | ||||
|   return Object.keys(obj) | ||||
|     .sort((a, b) => sortMethod === 'key_name' ? a.localeCompare(b) : b.localeCompare(a)) | ||||
|     .reduce((sortedObj, key) => { | ||||
|       sortedObj[key] = sortObjectKeys((obj as Record<string, unknown>)[key], sortMethod); | ||||
|       return sortedObj; | ||||
|     }, {} as Record<string, unknown>) as T; | ||||
| } | ||||
| 
 | ||||
| function sortObjectValues(obj: any, sortMethod: string, keyName: string): any { | ||||
|   if (Array.isArray(obj)) { | ||||
|     return obj.sort((a, b) => { | ||||
|       const valueA = a[keyName]; | ||||
|       const valueB = b[keyName]; | ||||
| 
 | ||||
|       if (typeof valueA === 'number' && typeof valueB === 'number') { | ||||
|         return sortMethod === 'key_val' ? valueA - valueB : valueB - valueA; | ||||
|       } | ||||
|       if (typeof valueA === 'string' && typeof valueB === 'string') { | ||||
|         return sortMethod === 'key_val' ? valueA.localeCompare(valueB) : valueB.localeCompare(valueA); | ||||
|       } | ||||
|       return 0; | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   if (typeof obj === 'object' && obj !== null) { | ||||
|     const sortedKeys = Object.keys(obj).sort((a, b) => { | ||||
|       if (sortMethod === 'key_val') { | ||||
|         return a.localeCompare(b); | ||||
|       } | ||||
|       return b.localeCompare(a); | ||||
|     }); | ||||
| 
 | ||||
|     return sortedKeys.reduce((sortedObj, key) => { | ||||
|       sortedObj[key] = sortObjectValues((obj as Record<string, unknown>)[key], sortMethod, keyName); | ||||
|       return sortedObj; | ||||
|     }, {} as Record<string, unknown>) as T; | ||||
|   } | ||||
| 
 | ||||
|   return obj; | ||||
| } | ||||
| 
 | ||||
| function formatJson({ | ||||
|   rawJson, | ||||
|   sortMethod = 'key_name', | ||||
|   keyName = '', | ||||
|   indentSize = 3, | ||||
| }: { | ||||
|   rawJson: MaybeRef<string> | ||||
|   sortMethod: MaybeRef<string> | ||||
|   keyName: MaybeRef<string> | ||||
|   indentSize?: MaybeRef<number> | ||||
| }) { | ||||
|   const parsedObject = JSON5.parse(get(rawJson)); | ||||
| 
 | ||||
|   if (['key_name', 'key_name_desc'].includes(get(sortMethod))) { | ||||
|     return JSON.stringify(sortObjectKeys(parsedObject, get(sortMethod)), null, get(indentSize)); | ||||
|   } | ||||
| 
 | ||||
|   return JSON.stringify(sortObjectValues(parsedObject, get(sortMethod), get(keyName)), null, get(indentSize)); | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user