Skip to content

Commit 3aa887c

Browse files
committed
feat(new tool): JSON Size Analyzer
Fix CorentinTh#450
1 parent 318fb6e commit 3aa887c

File tree

9 files changed

+321
-9
lines changed

9 files changed

+321
-9
lines changed

components.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ declare module '@vue/runtime-core' {
109109
Ipv6UlaGenerator: typeof import('./src/tools/ipv6-ula-generator/ipv6-ula-generator.vue')['default']
110110
JsonDiff: typeof import('./src/tools/json-diff/json-diff.vue')['default']
111111
JsonMinify: typeof import('./src/tools/json-minify/json-minify.vue')['default']
112+
JsonSizeAnalyzer: typeof import('./src/tools/json-size-analyzer/json-size-analyzer.vue')['default']
112113
JsonToCsv: typeof import('./src/tools/json-to-csv/json-to-csv.vue')['default']
113114
JsonToToml: typeof import('./src/tools/json-to-toml/json-to-toml.vue')['default']
114115
JsonToXml: typeof import('./src/tools/json-to-xml/json-to-xml.vue')['default']
@@ -138,13 +139,15 @@ declare module '@vue/runtime-core' {
138139
NH1: typeof import('naive-ui')['NH1']
139140
NH3: typeof import('naive-ui')['NH3']
140141
NIcon: typeof import('naive-ui')['NIcon']
142+
NInput: typeof import('naive-ui')['NInput']
141143
NInputNumber: typeof import('naive-ui')['NInputNumber']
142144
NLayout: typeof import('naive-ui')['NLayout']
143145
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
144146
NMenu: typeof import('naive-ui')['NMenu']
145147
NScrollbar: typeof import('naive-ui')['NScrollbar']
146148
NSlider: typeof import('naive-ui')['NSlider']
147149
NSwitch: typeof import('naive-ui')['NSwitch']
150+
NTree: typeof import('naive-ui')['NTree']
148151
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
149152
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']
150153
PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default']

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
"iarna-toml-esm": "^3.0.5",
6767
"ibantools": "^4.3.3",
6868
"js-base64": "^3.7.6",
69+
"json-analyzer": "^1.2.2",
6970
"json5": "^2.2.3",
7071
"jwt-decode": "^3.1.2",
7172
"libphonenumber-js": "^1.10.28",

pnpm-lock.yaml

Lines changed: 58 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/tools/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { tool as base64FileConverter } from './base64-file-converter';
22
import { tool as base64StringConverter } from './base64-string-converter';
33
import { tool as basicAuthGenerator } from './basic-auth-generator';
44
import { tool as emailNormalizer } from './email-normalizer';
5+
import { tool as jsonSizeAnalyzer } from './json-size-analyzer';
56

67
import { tool as asciiTextDrawer } from './ascii-text-drawer';
78

@@ -147,6 +148,7 @@ export const toolsByCategory: ToolCategory[] = [
147148
crontabGenerator,
148149
jsonViewer,
149150
jsonMinify,
151+
jsonSizeAnalyzer,
150152
jsonToCsv,
151153
sqlPrettify,
152154
chmodCalculator,

src/tools/json-size-analyzer/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { FileAnalytics } from '@vicons/tabler';
2+
import { defineTool } from '../tool';
3+
4+
export const tool = defineTool({
5+
name: 'Json Size Analyzer',
6+
path: '/json-size-analyzer',
7+
description: 'Measure JSON nodes relative weights',
8+
keywords: ['json', 'size', 'analyzer'],
9+
component: () => import('./json-size-analyzer.vue'),
10+
icon: FileAnalytics,
11+
createdAt: new Date('2024-07-14'),
12+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
declare module 'json-analyzer' {
2+
export default function analyze({
3+
json,
4+
verbose,
5+
maxDepth,
6+
target,
7+
}: {
8+
json: any,
9+
verbose: boolean,
10+
maxDepth: number,
11+
target: string,
12+
});
13+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { getJsonUsageTreeNodes } from './json-size-analyzer.service';
3+
4+
describe('json-size-analyzer', () => {
5+
describe('getJsonUsageTreeNodes', () => {
6+
it('return correct tree nodes structures', () => {
7+
expect(getJsonUsageTreeNodes([{ a: [1, 2, 3] }, { b: 'a' }])).to.deep.eq({
8+
children: [
9+
{
10+
children: [
11+
{
12+
children: [
13+
{
14+
children: [],
15+
key: '$.[0].a.[0]',
16+
label: '$.[0].a.[0]: 1 B(26 B gzip)',
17+
},
18+
{
19+
children: [],
20+
key: '$.[0].a.[1]',
21+
label: '$.[0].a.[1]: 1 B(24 B gzip)',
22+
},
23+
{
24+
children: [],
25+
key: '$.[0].a.[2]',
26+
label: '$.[0].a.[2]: 1 B(25 B gzip)',
27+
},
28+
],
29+
key: '$.[0].a',
30+
label: '$.[0].a: 7 B(35 B gzip) ; 28.000% of parent ; biggest child node: \'0\'',
31+
},
32+
],
33+
key: '$.[0]',
34+
label: '$.[0]: 13 B(43 B gzip) ; 52.000% of parent ; biggest child node: \'a\'',
35+
},
36+
{
37+
children: [
38+
{
39+
children: [],
40+
key: '$.[1].b',
41+
label: '$.[1].b: 1 B(25 B gzip)',
42+
},
43+
],
44+
key: '$.[1]',
45+
label: '$.[1]: 9 B(34 B gzip) ; 36.000% of parent ; biggest child node: \'b\'',
46+
},
47+
],
48+
key: '$',
49+
label: '$: 25 B(61 B gzip) ; 100.00% of parent ; biggest child node: \'0\'',
50+
});
51+
expect(getJsonUsageTreeNodes({ a: { b: [1, 2, 3], c: 12 } })).to.deep.eq({
52+
children: [
53+
{
54+
children: [
55+
{
56+
children: [
57+
{
58+
children: [],
59+
key: '$.a.b.[0]',
60+
label: '$.a.b.[0]: 1 B(26 B gzip)',
61+
},
62+
{
63+
children: [],
64+
key: '$.a.b.[1]',
65+
label: '$.a.b.[1]: 1 B(24 B gzip)',
66+
},
67+
{
68+
children: [],
69+
key: '$.a.b.[2]',
70+
label: '$.a.b.[2]: 1 B(25 B gzip)',
71+
},
72+
],
73+
key: '$.a.b',
74+
label: '$.a.b: 7 B(35 B gzip) ; 26.923% of parent ; biggest child node: \'0\'',
75+
},
76+
{
77+
children: [],
78+
key: '$.a.c',
79+
label: '$.a.c: 2 B(24 B gzip)',
80+
},
81+
],
82+
key: '$.a',
83+
label: '$.a: 20 B(50 B gzip) ; 76.923% of parent ; biggest child node: \'b\'',
84+
},
85+
],
86+
key: '$',
87+
label: '$: 26 B(63 B gzip) ; 100.00% of parent ; biggest child node: \'a\'',
88+
});
89+
expect(getJsonUsageTreeNodes({ a: { b: 'azerty', c: 'ueop' } })).to.deep.eq({
90+
children: [
91+
{
92+
children: [
93+
{
94+
children: [],
95+
key: '$.a.b',
96+
label: '$.a.b: 6 B(30 B gzip)',
97+
},
98+
{
99+
children: [],
100+
key: '$.a.c',
101+
label: '$.a.c: 4 B(29 B gzip)',
102+
},
103+
],
104+
key: '$.a',
105+
label: '$.a: 25 B(51 B gzip) ; 80.645% of parent ; biggest child node: \'b\'',
106+
},
107+
],
108+
key: '$',
109+
label: '$: 31 B(61 B gzip) ; 100.00% of parent ; biggest child node: \'a\'',
110+
});
111+
});
112+
});
113+
});
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import jsonAnalyzer from 'json-analyzer';
2+
3+
export interface Meta {
4+
__meta__: {
5+
size?: {
6+
value: number
7+
raw: string
8+
gzip: string
9+
}
10+
number_of_childs?: number
11+
parent_relative_percentage?: string
12+
biggest_node_child: string
13+
}
14+
}
15+
export type AnalysisNode = {
16+
[key: string]: object & Meta
17+
} & Meta;
18+
19+
export type TreeNode = {
20+
key: string
21+
label: string
22+
children: Array<TreeNode>
23+
} & Record<string, unknown>;
24+
25+
function getTreeNodes(obj: AnalysisNode, parentName: string): TreeNode {
26+
const childNodes = Object.entries(obj)
27+
.filter(([key, v]) => key !== '__meta__' && typeof v === 'object')
28+
.map(([k, v]) => ({
29+
key: (Number.isNaN(Number.parseInt(k, 10)) ? `.${k}` : `.[${k}]`),
30+
value: v as AnalysisNode,
31+
}));
32+
const biggest_child_node = obj.__meta__.biggest_node_child ? ` ; biggest child node: '${obj.__meta__.biggest_node_child}'` : '';
33+
const parent_relative_percentage = obj.__meta__.parent_relative_percentage ? ` ; ${obj.__meta__.parent_relative_percentage} of parent` : '';
34+
return {
35+
key: parentName,
36+
label: obj.__meta__
37+
? `${parentName}: ${obj.__meta__.size?.raw}(${obj.__meta__.size?.gzip} gzip)${parent_relative_percentage}${biggest_child_node}`
38+
: parentName,
39+
children: childNodes.map(childNode => getTreeNodes(childNode.value, parentName + childNode.key)),
40+
};
41+
}
42+
43+
export function getJsonUsageTreeNodes(jsonObj: any, maxDepth: number = 100, targetNode: string = ''): TreeNode {
44+
const analysis = jsonAnalyzer({
45+
json: jsonObj,
46+
verbose: true,
47+
maxDepth,
48+
target: targetNode,
49+
});
50+
return getTreeNodes(analysis, '$');
51+
}

0 commit comments

Comments
 (0)