Skip to content

Commit efd30f9

Browse files
committed
Merge branch 'feat/json-size' into chore/all-my-stuffs
# Conflicts: # package.json # pnpm-lock.yaml # src/tools/index.ts
2 parents 060c9b2 + 8545fc6 commit efd30f9

File tree

8 files changed

+303
-1
lines changed

8 files changed

+303
-1
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@
130130
"image-in-browser": "^3.1.0",
131131
"js-base64": "^3.7.7",
132132
"json-editor-vue": "^0.16.0",
133+
"json-analyzer": "^1.2.2",
133134
"json5": "^2.2.3",
134135
"jsonpath": "^1.1.1",
135136
"jsonar-mod": "^1.9.0",

pnpm-lock.yaml

Lines changed: 43 additions & 1 deletion
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
@@ -22,6 +22,7 @@ import { tool as jqMemo } from './jq-memo';
2222
import { tool as jsUnobfuscator } from './js-unobfuscator';
2323
import { tool as jsonToPhpArray } from './json-to-php-array';
2424
import { tool as phpArrayToJson } from './php-array-to-json';
25+
import { tool as jsonSizeAnalyzer } from './json-size-analyzer';
2526

2627
import { tool as cssXpathConverter } from './css-xpath-converter';
2728
import { tool as cssSelectorsMemo } from './css-selectors-memo';
@@ -233,6 +234,7 @@ export const toolsByCategory: ToolCategory[] = [
233234
crontabGenerator,
234235
jsonViewer,
235236
jsonMinify,
237+
jsonSizeAnalyzer,
236238
jsonToCsv,
237239
sqlPrettify,
238240
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+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<script setup lang="ts">
2+
import JSON5 from 'json5';
3+
import { getJsonUsageTreeNodes } from './json-size-analyzer.service';
4+
import { useValidation } from '@/composable/validation';
5+
6+
const json = ref('{"a": 1, "b": [1,2,3]}');
7+
const maxDepth = ref(100);
8+
const target = ref('');
9+
10+
const jsonSizes = computed(() => {
11+
const jsonObj = JSON5.parse(json.value);
12+
if (!jsonObj) {
13+
return null;
14+
}
15+
return [getJsonUsageTreeNodes(jsonObj, maxDepth.value - 1, target.value)];
16+
});
17+
const searchInAnalysis = ref('');
18+
19+
const jsonValidation = useValidation({
20+
source: json,
21+
rules: [
22+
{
23+
validator: (v) => {
24+
return JSON5.parse(v);
25+
},
26+
message: 'Provided JSON is not valid.',
27+
},
28+
],
29+
});
30+
</script>
31+
32+
<template>
33+
<div style="max-width: 600px;">
34+
<c-card title="Input" mb-2>
35+
<c-input-text
36+
v-model:value="json"
37+
label="JSON"
38+
multiline
39+
placeholder="Put your JSON data here..."
40+
rows="5"
41+
:validation="jsonValidation"
42+
mb-2
43+
/>
44+
45+
<n-form-item label="Max Depth:" label-placement="left">
46+
<n-input-number v-model:value="maxDepth" :min="0" w-full />
47+
</n-form-item>
48+
49+
<c-input-text
50+
v-model:value="target"
51+
label="Target Node"
52+
placeholder="Where to start the analyze (ie, a[0].b.c)"
53+
mb-2
54+
/>
55+
</c-card>
56+
57+
<c-card v-if="jsonSizes" title="Analysis">
58+
<n-input v-model:value="searchInAnalysis" placeholder="Search in result" />
59+
<n-tree
60+
:show-irrelevant-nodes="false"
61+
:pattern="searchInAnalysis"
62+
:default-expand-all="true"
63+
:data="jsonSizes"
64+
block-line
65+
/>
66+
</c-card>
67+
</div>
68+
</template>

0 commit comments

Comments
 (0)