diff --git a/src/tools/index.ts b/src/tools/index.ts index b4c161ef8..5f5d1fb26 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -2,6 +2,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 emailNormalizer } from './email-normalizer'; +import { tool as jsonSizeAnalyzer } from './json-size-analyzer'; import { tool as asciiTextDrawer } from './ascii-text-drawer'; @@ -147,6 +148,7 @@ export const toolsByCategory: ToolCategory[] = [ crontabGenerator, jsonViewer, jsonMinify, + jsonSizeAnalyzer, jsonToCsv, sqlPrettify, chmodCalculator, diff --git a/src/tools/json-size-analyzer/index.ts b/src/tools/json-size-analyzer/index.ts new file mode 100644 index 000000000..2e11977db --- /dev/null +++ b/src/tools/json-size-analyzer/index.ts @@ -0,0 +1,12 @@ +import { FileAnalytics } from '@vicons/tabler'; +import { defineTool } from '../tool'; + +export const tool = defineTool({ + name: 'Json Size Analyzer', + path: '/json-size-analyzer', + description: 'Measure JSON nodes relative weights', + keywords: ['json', 'size', 'analyzer'], + component: () => import('./json-size-analyzer.vue'), + icon: FileAnalytics, + createdAt: new Date('2024-07-14'), +}); diff --git a/src/tools/json-size-analyzer/json-size-analyzer.service.test.ts b/src/tools/json-size-analyzer/json-size-analyzer.service.test.ts new file mode 100644 index 000000000..dd2a9e2da --- /dev/null +++ b/src/tools/json-size-analyzer/json-size-analyzer.service.test.ts @@ -0,0 +1,113 @@ +import { describe, expect, it } from 'vitest'; +import { getJsonUsageTreeNodes } from './json-size-analyzer.service'; + +describe('json-size-analyzer', () => { + describe('getJsonUsageTreeNodes', () => { + it('return correct tree nodes structures', () => { + expect(getJsonUsageTreeNodes([{ a: [1, 2, 3] }, { b: 'a' }])).to.deep.eq({ + children: [ + { + children: [ + { + children: [ + { + children: [], + key: '$.[0].a.[0]', + label: '$.[0].a.[0]: 1 B(26 B gzip)', + }, + { + children: [], + key: '$.[0].a.[1]', + label: '$.[0].a.[1]: 1 B(24 B gzip)', + }, + { + children: [], + key: '$.[0].a.[2]', + label: '$.[0].a.[2]: 1 B(25 B gzip)', + }, + ], + key: '$.[0].a', + label: '$.[0].a: 7 B(35 B gzip) ; 28.000% of parent ; biggest child node: \'0\'', + }, + ], + key: '$.[0]', + label: '$.[0]: 13 B(43 B gzip) ; 52.000% of parent ; biggest child node: \'a\'', + }, + { + children: [ + { + children: [], + key: '$.[1].b', + label: '$.[1].b: 1 B(25 B gzip)', + }, + ], + key: '$.[1]', + label: '$.[1]: 9 B(34 B gzip) ; 36.000% of parent ; biggest child node: \'b\'', + }, + ], + key: '$', + label: '$: 25 B(61 B gzip) ; 100.00% of parent ; biggest child node: \'0\'', + }); + expect(getJsonUsageTreeNodes({ a: { b: [1, 2, 3], c: 12 } })).to.deep.eq({ + children: [ + { + children: [ + { + children: [ + { + children: [], + key: '$.a.b.[0]', + label: '$.a.b.[0]: 1 B(26 B gzip)', + }, + { + children: [], + key: '$.a.b.[1]', + label: '$.a.b.[1]: 1 B(24 B gzip)', + }, + { + children: [], + key: '$.a.b.[2]', + label: '$.a.b.[2]: 1 B(25 B gzip)', + }, + ], + key: '$.a.b', + label: '$.a.b: 7 B(35 B gzip) ; 26.923% of parent ; biggest child node: \'0\'', + }, + { + children: [], + key: '$.a.c', + label: '$.a.c: 2 B(24 B gzip)', + }, + ], + key: '$.a', + label: '$.a: 20 B(50 B gzip) ; 76.923% of parent ; biggest child node: \'b\'', + }, + ], + key: '$', + label: '$: 26 B(63 B gzip) ; 100.00% of parent ; biggest child node: \'a\'', + }); + expect(getJsonUsageTreeNodes({ a: { b: 'azerty', c: 'ueop' } })).to.deep.eq({ + children: [ + { + children: [ + { + children: [], + key: '$.a.b', + label: '$.a.b: 6 B(30 B gzip)', + }, + { + children: [], + key: '$.a.c', + label: '$.a.c: 4 B(29 B gzip)', + }, + ], + key: '$.a', + label: '$.a: 25 B(51 B gzip) ; 80.645% of parent ; biggest child node: \'b\'', + }, + ], + key: '$', + label: '$: 31 B(61 B gzip) ; 100.00% of parent ; biggest child node: \'a\'', + }); + }); + }); +}); diff --git a/src/tools/json-size-analyzer/json-size-analyzer.service.ts b/src/tools/json-size-analyzer/json-size-analyzer.service.ts new file mode 100644 index 000000000..36a8890e1 --- /dev/null +++ b/src/tools/json-size-analyzer/json-size-analyzer.service.ts @@ -0,0 +1,49 @@ +import jsonAnalyzer from 'json-analyzer'; + +export interface Meta { + __meta__: { + size?: { + value: number + raw: string + gzip: string + } + number_of_childs?: number + parent_relative_percentage?: string + biggest_node_child: string + } +} +export type AnalysisNode = { + [key: string]: object & Meta +} & Meta; + +export type TreeNode = { + key: string + label: string + children: Array +} & Record; + +function getTreeNodes(obj: AnalysisNode, parentName: string): TreeNode { + const childNodes = Object.entries(obj) + .filter(([key, v]) => key !== '__meta__' && typeof v === 'object') + .map(([k, v]) => ({ + key: (Number.isNaN(Number.parseInt(k, 10)) ? `.${k}` : `.[${k}]`), + value: v as AnalysisNode, + })); + return { + key: parentName, + label: obj.__meta__ + ? `${parentName}: ${obj.__meta__.size?.raw}(${obj.__meta__.size?.gzip} gzip)${obj.__meta__.parent_relative_percentage ? ` ; ${obj.__meta__.parent_relative_percentage} of parent` : ''}${obj.__meta__.biggest_node_child ? ` ; biggest child node: '${obj.__meta__.biggest_node_child}'` : ''}` + : parentName, + children: childNodes.map(childNode => getTreeNodes(childNode.value, parentName + childNode.key)), + }; +} + +export function getJsonUsageTreeNodes(jsonObj: any, maxDepth: number = 100, targetNode: string = ''): TreeNode { + const analysis = jsonAnalyzer({ + json: jsonObj, + verbose: true, + maxDepth, + target: targetNode, + }); + return getTreeNodes(analysis, '$'); +} diff --git a/src/tools/json-size-analyzer/json-size-analyzer.vue b/src/tools/json-size-analyzer/json-size-analyzer.vue new file mode 100644 index 000000000..b5e873b90 --- /dev/null +++ b/src/tools/json-size-analyzer/json-size-analyzer.vue @@ -0,0 +1,67 @@ + + +