Skip to content

Commit fc088bd

Browse files
committed
feat: [destr] 增加destr方法用于解析JSON
1 parent 7c0fe9e commit fc088bd

File tree

3 files changed

+263
-90
lines changed

3 files changed

+263
-90
lines changed

src/tools/destr.ts

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
const suspectProtoRx
2+
= /"(?:_|\\u0{2}5[Ff]){2}(?:p|\\u0{2}70)(?:r|\\u0{2}72)(?:o|\\u0{2}6[Ff])(?:t|\\u0{2}74)(?:o|\\u0{2}6[Ff])(?:_|\\u0{2}5[Ff]){2}"\s*:/
3+
const suspectConstructorRx
4+
= /"(?:c|\\u0063)(?:o|\\u006[Ff])(?:n|\\u006[Ee])(?:s|\\u0073)(?:t|\\u0074)(?:r|\\u0072)(?:u|\\u0075)(?:c|\\u0063)(?:t|\\u0074)(?:o|\\u006[Ff])(?:r|\\u0072)"\s*:/
5+
// eslint-disable-next-line
6+
const JsonSigRx = /^\s*["[{]|^\s*-?\d{1,16}(\.\d{1,17})?(E[+-]?\d+)?\s*$/i
7+
8+
function jsonParseTransform(key: string, value: any): any {
9+
if (
10+
key === '__proto__'
11+
|| (key === 'constructor'
12+
&& value
13+
&& typeof value === 'object'
14+
&& 'prototype' in value)
15+
) {
16+
warnKeyDropped(key)
17+
return
18+
}
19+
return value
20+
}
21+
22+
function warnKeyDropped(key: string): void {
23+
console.warn(`[destr] Dropping "${key}" key to prevent prototype pollution.`)
24+
}
25+
26+
export interface Options {
27+
strict?: boolean
28+
customVal?: any
29+
}
30+
/**
31+
* @description A faster, secure and convenient alternative for `JSON.parse`
32+
* @param value The value to be parsed
33+
* @param options The options
34+
* @returns parsed value
35+
* @category tools
36+
* @example
37+
* ```
38+
* // Returns "[foo"
39+
* destr("[foo");
40+
* // Return is not valid JSON
41+
* Json.parse("[foo")
42+
* ```
43+
*/
44+
export function destr<T = unknown>(value: any, options: Options = {}): T {
45+
if (typeof value !== 'string') {
46+
return value
47+
}
48+
49+
const _value = value.trim()
50+
if (
51+
52+
value[0] === '"'
53+
&& value.endsWith('"')
54+
&& !value.includes('\\')
55+
) {
56+
return _value.slice(1, -1) as T
57+
}
58+
59+
if (_value.length <= 9) {
60+
const _lval = _value.toLowerCase()
61+
if (_lval === 'true') {
62+
return true as T
63+
}
64+
if (_lval === 'false') {
65+
return false as T
66+
}
67+
if (_lval === 'undefined') {
68+
return undefined as T
69+
}
70+
if (_lval === 'null') {
71+
return null as T
72+
}
73+
if (_lval === 'nan') {
74+
return Number.NaN as T
75+
}
76+
if (_lval === 'infinity') {
77+
return Number.POSITIVE_INFINITY as T
78+
}
79+
if (_lval === '-infinity') {
80+
return Number.NEGATIVE_INFINITY as T
81+
}
82+
}
83+
84+
if (!JsonSigRx.test(value)) {
85+
if (options.customVal !== undefined) {
86+
return options.customVal as T
87+
}
88+
if (options.strict) {
89+
throw new SyntaxError('[destr] Invalid JSON')
90+
}
91+
return value as T
92+
}
93+
94+
try {
95+
if (suspectProtoRx.test(value) || suspectConstructorRx.test(value)) {
96+
if (options.customVal !== undefined) {
97+
return options.customVal as T
98+
}
99+
if (options.strict) {
100+
throw new Error('[destr] Possible prototype pollution')
101+
}
102+
return JSON.parse(value, jsonParseTransform)
103+
}
104+
return JSON.parse(value)
105+
}
106+
catch (error) {
107+
if (options.customVal !== undefined) {
108+
return options.customVal as T
109+
}
110+
if (options.strict) {
111+
throw error
112+
}
113+
return value as T
114+
}
115+
}
116+
/**
117+
* @description A faster, secure and convenient alternative for `JSON.parse`
118+
* @param value The value to be parsed
119+
* @param options The options
120+
* @returns parsed value
121+
* @category tools
122+
* @example
123+
* ```
124+
* // Throws an error
125+
* safeDestr("[foo");
126+
* // Return is not valid JSON
127+
* Json.parse("[foo")
128+
* ```
129+
*/
130+
export function safeDestr<T = unknown>(value: any, options: Options = {}): T {
131+
return destr<T>(value, { ...options, strict: true })
132+
}
133+
134+
/**
135+
* @description A faster, secure and convenient alternative for `JSON.parse`
136+
* @param value The value to be parsed
137+
* @param options The options
138+
* @returns parsed value
139+
* @category tools
140+
* @example
141+
* ```
142+
* // Returns "defaultVal"
143+
* customDestr<string>("[foo", { customVal: "defaultVal" });
144+
* // Return is not valid JSON
145+
* Json.parse("[foo")
146+
* ```
147+
*/
148+
export function customDestr<T = unknown>(value: any, options: Options = {}): T {
149+
if (options.customVal === undefined)
150+
return destr<T>(value, { ...options, customVal: null })
151+
return destr<T>(value, { ...options })
152+
}
153+
154+
export default destr

src/tools/index.ts

Lines changed: 7 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,10 @@
1-
/**
2-
* @description 获取文件类型
3-
* @param url 文件地址
4-
* @returns { image, video, pdf, document, audio, zip, excel, ppt, code, executable, presentation, other }
5-
*/
1+
import other from './other'
2+
import destr from './destr'
63

7-
export function getFileType(url: string): string {
8-
if (!url)
9-
return 'other'
10-
const fileTypes: { [key: string]: string[] } = {
11-
image: ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'],
12-
video: ['mp4', 'avi', 'mov'],
13-
pdf: ['pdf'],
14-
document: ['doc', 'docx', 'txt'],
15-
audio: ['mp3', 'wav', 'ogg'],
16-
zip: ['zip', 'rar', '7z'],
17-
excel: ['xls', 'xlsx', 'csv'],
18-
ppt: ['ppt', 'pptx'],
19-
code: ['js', 'html', 'css', 'java', 'cpp', 'py'],
20-
executable: ['exe', 'msi'],
21-
presentation: ['key'],
22-
}
23-
const fileExtension = url.split('.').pop()?.toLowerCase()
24-
for (const fileType in fileTypes) {
25-
if (fileTypes[fileType].includes(fileExtension!))
26-
return fileType
27-
}
28-
return 'other'
29-
}
30-
31-
/**
32-
* @description 格式化数字,添加千位分隔符
33-
* @param num 数字
34-
* @returns { string } string:格式化后的数字
35-
*/
36-
export function formatNumber(num: number): string {
37-
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
38-
}
39-
40-
/**
41-
* @description 生成指定长度的随机字符串
42-
* @param length 长度
43-
* @returns { string } string:随机字符串
44-
*/
45-
export function randomString(length: number): string {
46-
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
47-
let result = ''
48-
for (let i = 0; i < length; i++)
49-
result += characters.charAt(Math.floor(Math.random() * characters.length))
50-
return result
51-
}
52-
53-
/**
54-
* @description 防抖函数
55-
* @param func 函数
56-
* @param delay 延迟时间
57-
* @returns { Function } Function:防抖函数
58-
*/
59-
export function debounce(func: Function, delay: number): Function {
60-
let timer: NodeJS.Timeout
61-
return function (this: any, ...args: any[]) {
62-
clearTimeout(timer)
63-
timer = setTimeout(() => {
64-
func.apply(this, args)
65-
}, delay)
66-
}
67-
}
4+
export * from './other'
5+
export * from './destr'
686

69-
/**
70-
* @description 节流函数
71-
* @param func 函数
72-
* @param delay 延迟时间
73-
* @returns { Function } Function:节流函数
74-
*/
75-
export function throttle(func: Function, delay: number): Function {
76-
let timer: NodeJS.Timeout
77-
let lastExecTime = 0
78-
return function (this: any, ...args: any[]) {
79-
const currentTime = Date.now()
80-
const remainingTime = delay - (currentTime - lastExecTime)
81-
clearTimeout(timer)
82-
if (remainingTime <= 0) {
83-
func.apply(this, args)
84-
lastExecTime = currentTime
85-
}
86-
else {
87-
timer = setTimeout(() => {
88-
func.apply(this, args)
89-
lastExecTime = Date.now()
90-
}, remainingTime)
91-
}
92-
}
7+
export const UTools = {
8+
...other,
9+
...destr,
9310
}

src/tools/other.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/**
2+
* @description 获取文件类型
3+
* @param url 文件地址
4+
* @returns { image, video, pdf, document, audio, zip, excel, ppt, code, executable, presentation, other }
5+
*/
6+
7+
export function getFileType(url: string): string {
8+
if (!url)
9+
return 'other'
10+
const fileTypes: { [key: string]: string[] } = {
11+
image: ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'],
12+
video: ['mp4', 'avi', 'mov'],
13+
pdf: ['pdf'],
14+
document: ['doc', 'docx', 'txt'],
15+
audio: ['mp3', 'wav', 'ogg'],
16+
zip: ['zip', 'rar', '7z'],
17+
excel: ['xls', 'xlsx', 'csv'],
18+
ppt: ['ppt', 'pptx'],
19+
code: ['js', 'html', 'css', 'java', 'cpp', 'py'],
20+
executable: ['exe', 'msi'],
21+
presentation: ['key'],
22+
}
23+
const fileExtension = url.split('.').pop()?.toLowerCase()
24+
for (const fileType in fileTypes) {
25+
if (fileTypes[fileType].includes(fileExtension!))
26+
return fileType
27+
}
28+
return 'other'
29+
}
30+
31+
/**
32+
* @description 格式化数字,添加千位分隔符
33+
* @param num 数字
34+
* @returns { string } string:格式化后的数字
35+
*/
36+
export function formatNumber(num: number): string {
37+
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
38+
}
39+
40+
/**
41+
* @description 生成指定长度的随机字符串
42+
* @param length 长度
43+
* @returns { string } string:随机字符串
44+
*/
45+
export function randomString(length: number): string {
46+
const characters
47+
= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
48+
let result = ''
49+
for (let i = 0; i < length; i++)
50+
result += characters.charAt(Math.floor(Math.random() * characters.length))
51+
return result
52+
}
53+
54+
/**
55+
* @description 防抖函数
56+
* @param func 函数
57+
* @param delay 延迟时间
58+
* @returns { Function } Function:防抖函数
59+
*/
60+
export function debounce(func: Function, delay: number): Function {
61+
let timer: NodeJS.Timeout
62+
return function (this: any, ...args: any[]) {
63+
clearTimeout(timer)
64+
timer = setTimeout(() => {
65+
func.apply(this, args)
66+
}, delay)
67+
}
68+
}
69+
70+
/**
71+
* @description 节流函数
72+
* @param func 函数
73+
* @param delay 延迟时间
74+
* @returns { Function } Function:节流函数
75+
*/
76+
export function throttle(func: Function, delay: number): Function {
77+
let timer: NodeJS.Timeout
78+
let lastExecTime = 0
79+
return function (this: any, ...args: any[]) {
80+
const currentTime = Date.now()
81+
const remainingTime = delay - (currentTime - lastExecTime)
82+
clearTimeout(timer)
83+
if (remainingTime <= 0) {
84+
func.apply(this, args)
85+
lastExecTime = currentTime
86+
}
87+
else {
88+
timer = setTimeout(() => {
89+
func.apply(this, args)
90+
lastExecTime = Date.now()
91+
}, remainingTime)
92+
}
93+
}
94+
}
95+
96+
export default {
97+
getFileType,
98+
formatNumber,
99+
randomString,
100+
debounce,
101+
throttle,
102+
}

0 commit comments

Comments
 (0)