This commit is contained in:
sharevb 2024-12-13 13:14:26 -03:00 committed by GitHub
commit 6759a97ec7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 321 additions and 7 deletions

9
components.d.ts vendored
View file

@ -109,6 +109,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']
JsonSizeAnalyzer: typeof import('./src/tools/json-size-analyzer/json-size-analyzer.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']
JsonToXml: typeof import('./src/tools/json-to-xml/json-to-xml.vue')['default']
@ -138,11 +139,15 @@ declare module '@vue/runtime-core' {
NH1: typeof import('naive-ui')['NH1']
NH3: typeof import('naive-ui')['NH3']
NIcon: typeof import('naive-ui')['NIcon']
NInput: typeof import('naive-ui')['NInput']
NInputNumber: typeof import('naive-ui')['NInputNumber']
NLayout: typeof import('naive-ui')['NLayout']
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
NMenu: typeof import('naive-ui')['NMenu']
NSpace: typeof import('naive-ui')['NSpace']
NTable: typeof import('naive-ui')['NTable']
NScrollbar: typeof import('naive-ui')['NScrollbar']
NSlider: typeof import('naive-ui')['NSlider']
NSwitch: typeof import('naive-ui')['NSwitch']
NTree: typeof import('naive-ui')['NTree']
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']

View file

@ -70,6 +70,7 @@
"iarna-toml-esm": "^3.0.5",
"ibantools": "^4.3.3",
"js-base64": "^3.7.6",
"json-analyzer": "^1.2.2",
"json5": "^2.2.3",
"jwt-decode": "^3.1.2",
"libphonenumber-js": "^1.10.28",

59
pnpm-lock.yaml generated
View file

@ -107,6 +107,9 @@ dependencies:
js-base64:
specifier: ^3.7.6
version: 3.7.7
json-analyzer:
specifier: ^1.2.2
version: 1.2.2
json5:
specifier: ^2.2.3
version: 2.2.3
@ -3412,7 +3415,7 @@ packages:
dependencies:
'@unhead/dom': 0.5.1
'@unhead/schema': 0.5.1
'@vueuse/shared': 11.0.3(vue@3.3.4)
'@vueuse/shared': 11.1.0(vue@3.3.4)
unhead: 0.5.1
vue: 3.3.4
transitivePeerDependencies:
@ -4054,8 +4057,8 @@ packages:
- vue
dev: false
/@vueuse/shared@11.0.3(vue@3.3.4):
resolution: {integrity: sha512-0rY2m6HS5t27n/Vp5cTDsKTlNnimCqsbh/fmT2LgE+aaU42EMfXo8+bNX91W9I7DDmxfuACXMmrd7d79JxkqWA==}
/@vueuse/shared@11.1.0(vue@3.3.4):
resolution: {integrity: sha512-YUtIpY122q7osj+zsNMFAfMTubGz0sn5QzE5gPzAIiCmtt2ha3uQUY1+JPyL4gRCTsLPX82Y9brNbo/aqlA91w==}
dependencies:
vue-demi: 0.14.10(vue@3.3.4)
transitivePeerDependencies:
@ -4581,6 +4584,11 @@ packages:
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
dev: true
/colors@1.4.0:
resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==}
engines: {node: '>=0.1.90'}
dev: false
/combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
@ -6636,6 +6644,21 @@ packages:
hasBin: true
dev: true
/json-analyzer@1.2.2:
resolution: {integrity: sha512-3xWTxyIggxOYIPT9NkucQPxlOBPJY14/ifely3eCtifE5pAxJXl/jUmSDwq+fLVhHIIj9MIJ6Bv7u3ItChG8vQ==}
engines: {node: '>=8.0.0'}
hasBin: true
dependencies:
commander: 2.20.3
lodash.flowright: 3.5.0
lodash.get: 4.4.2
lodash.isobject: 3.0.2
pako: 1.0.11
pretty-bytes: 5.6.0
prettyjson: 1.2.5
utf8-length: 0.0.1
dev: false
/json-parse-even-better-errors@2.3.1:
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
dev: true
@ -6775,6 +6798,18 @@ packages:
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
dev: true
/lodash.flowright@3.5.0:
resolution: {integrity: sha512-YxTYuodkvyINbDInmFcGGvkQwoAuoGUYosqstRTr5eq63GQt7WQ2xFU0wG1UfdbKYPwevd3zWDd6ybEE2g6qvA==}
dev: false
/lodash.get@4.4.2:
resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==}
dev: false
/lodash.isobject@3.0.2:
resolution: {integrity: sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA==}
dev: false
/lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
dev: true
@ -7022,7 +7057,6 @@ packages:
/minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
dev: true
/mlly@1.4.0:
resolution: {integrity: sha512-ua8PAThnTwpprIaU47EPeZ/bPUVp2QYBbWMphUQpVdBI3Lgqzm5KZQ45Agm3YJedHXaIHl6pBGabaLSUPPSptg==}
@ -7341,6 +7375,10 @@ packages:
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
engines: {node: '>=6'}
/pako@1.0.11:
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
dev: false
/param-case@2.1.1:
resolution: {integrity: sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==}
dependencies:
@ -7573,7 +7611,6 @@ packages:
/pretty-bytes@5.6.0:
resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==}
engines: {node: '>=6'}
dev: true
/pretty-bytes@6.1.0:
resolution: {integrity: sha512-Rk753HI8f4uivXi4ZCIYdhmG1V+WKzvRMg/X+M42a6t7D07RcmopXJMDNk6N++7Bl75URRGsb40ruvg7Hcp2wQ==}
@ -7589,6 +7626,14 @@ packages:
react-is: 18.2.0
dev: true
/prettyjson@1.2.5:
resolution: {integrity: sha512-rksPWtoZb2ZpT5OVgtmy0KHVM+Dca3iVwWY9ifwhcexfjebtgjg3wmrUt9PvJ59XIYBcknQeYHD8IAnVlh9lAw==}
hasBin: true
dependencies:
colors: 1.4.0
minimist: 1.2.8
dev: false
/prosemirror-changeset@2.2.1:
resolution: {integrity: sha512-J7msc6wbxB4ekDFj+n9gTW/jav/p53kdlivvuppHsrZXCaQdVgRghoZbSS3kwrRyAstRVQ4/+u5k7YfLgkkQvQ==}
dependencies:
@ -9048,6 +9093,10 @@ packages:
requires-port: 1.0.0
dev: true
/utf8-length@0.0.1:
resolution: {integrity: sha512-j/XH2ftofBiobnyApxlN/J6j/ixwT89WEjDcjT66d2i0+GIn9RZfzt8lpEXXE4jUe4NsjBSUq70kS2euQ4nnMw==}
dev: false
/util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
dev: true

View file

@ -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';
@ -151,6 +152,7 @@ export const toolsByCategory: ToolCategory[] = [
crontabGenerator,
jsonViewer,
jsonMinify,
jsonSizeAnalyzer,
jsonToCsv,
sqlPrettify,
chmodCalculator,

View file

@ -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'),
});

View file

@ -0,0 +1,13 @@
declare module 'json-analyzer' {
export default function analyze({
json,
verbose,
maxDepth,
target,
}: {
json: any,
verbose: boolean,
maxDepth: number,
target: string,
});
}

View file

@ -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\'',
});
});
});
});

View file

@ -0,0 +1,51 @@
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<TreeNode>
} & Record<string, unknown>;
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,
}));
const biggest_child_node = obj.__meta__.biggest_node_child ? ` ; biggest child node: '${obj.__meta__.biggest_node_child}'` : '';
const parent_relative_percentage = obj.__meta__.parent_relative_percentage ? ` ; ${obj.__meta__.parent_relative_percentage} of parent` : '';
return {
key: parentName,
label: obj.__meta__
? `${parentName}: ${obj.__meta__.size?.raw}(${obj.__meta__.size?.gzip} gzip)${parent_relative_percentage}${biggest_child_node}`
: 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, '$');
}

View file

@ -0,0 +1,68 @@
<script setup lang="ts">
import JSON5 from 'json5';
import { getJsonUsageTreeNodes } from './json-size-analyzer.service';
import { useValidation } from '@/composable/validation';
const json = ref('{"a": 1, "b": [1,2,3]}');
const maxDepth = ref(100);
const target = ref('');
const jsonSizes = computed(() => {
const jsonObj = JSON5.parse(json.value);
if (!jsonObj) {
return null;
}
return [getJsonUsageTreeNodes(jsonObj, maxDepth.value - 1, target.value)];
});
const searchInAnalysis = ref('');
const jsonValidation = useValidation({
source: json,
rules: [
{
validator: (v) => {
return JSON5.parse(v);
},
message: 'Provided JSON is not valid.',
},
],
});
</script>
<template>
<div style="max-width: 600px;">
<c-card title="Input" mb-2>
<c-input-text
v-model:value="json"
label="JSON"
multiline
placeholder="Put your JSON data here..."
rows="5"
:validation="jsonValidation"
mb-2
/>
<n-form-item label="Max Depth:" label-placement="left">
<n-input-number v-model:value="maxDepth" :min="0" w-full />
</n-form-item>
<c-input-text
v-model:value="target"
label="Target Node"
placeholder="Where to start the analyze (ie, a[0].b.c)"
mb-2
/>
</c-card>
<c-card v-if="jsonSizes" title="Analysis">
<n-input v-model:value="searchInAnalysis" placeholder="Search in result" />
<n-tree
:show-irrelevant-nodes="false"
:pattern="searchInAnalysis"
:default-expand-all="true"
:data="jsonSizes"
block-line
/>
</c-card>
</div>
</template>