-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathparse.ts
More file actions
159 lines (153 loc) · 4.67 KB
/
parse.ts
File metadata and controls
159 lines (153 loc) · 4.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/**
* @fileoverview JSON parsing utilities with Buffer detection and BOM stripping.
* Provides safe JSON parsing with automatic encoding handling.
*/
import { stripBom } from '../strings'
import type { JsonParseOptions, JsonPrimitive, JsonValue } from './types'
// IMPORTANT: Do not use destructuring here - use direct assignment instead.
// tsgo has a bug that incorrectly transpiles destructured exports, resulting in
// `exports.SomeName = void 0;` which causes runtime errors.
// See: https://github.com/SocketDev/socket-packageurl-js/issues/3
const JSONParse = JSON.parse
/**
* Check if a value is a Buffer instance.
* Uses duck-typing to detect Buffer without requiring Node.js Buffer in type system.
*
* @param x - Value to check
* @returns `true` if value is a Buffer, `false` otherwise
*
* @example
* ```ts
* isBuffer(Buffer.from('hello')) // => true
* isBuffer('hello') // => false
* isBuffer({ length: 5 }) // => false
* ```
*/
/*@__NO_SIDE_EFFECTS__*/
function isBuffer(x: unknown): x is Buffer {
if (!x || typeof x !== 'object') {
return false
}
const obj = x as Record<string | number, unknown>
if (typeof obj['length'] !== 'number') {
return false
}
if (typeof obj['copy'] !== 'function' || typeof obj['slice'] !== 'function') {
return false
}
if (
typeof obj['length'] === 'number' &&
obj['length'] > 0 &&
typeof obj[0] !== 'number'
) {
return false
}
const Ctor = (x as { constructor?: unknown }).constructor as
| { isBuffer?: unknown }
| undefined
return !!(typeof Ctor?.isBuffer === 'function' && Ctor.isBuffer(x))
}
/**
* Check if a value is a JSON primitive type.
* JSON primitives are: `null`, `boolean`, `number`, or `string`.
*
* @param value - Value to check
* @returns `true` if value is a JSON primitive, `false` otherwise
*
* @example
* ```ts
* isJsonPrimitive(null) // => true
* isJsonPrimitive(true) // => true
* isJsonPrimitive(42) // => true
* isJsonPrimitive('hello') // => true
* isJsonPrimitive({}) // => false
* isJsonPrimitive([]) // => false
* isJsonPrimitive(undefined) // => false
* ```
*/
/*@__NO_SIDE_EFFECTS__*/
export function isJsonPrimitive(value: unknown): value is JsonPrimitive {
return (
value === null ||
typeof value === 'boolean' ||
typeof value === 'number' ||
typeof value === 'string'
)
}
/**
* Parse JSON content with automatic Buffer handling and BOM stripping.
* Provides safer JSON parsing with helpful error messages and optional error suppression.
*
* Features:
* - Automatic UTF-8 Buffer conversion
* - BOM (Byte Order Mark) stripping for cross-platform compatibility
* - Enhanced error messages with filepath context
* - Optional error suppression (returns `undefined` instead of throwing)
* - Optional reviver for transforming parsed values
*
* @param content - JSON string or Buffer to parse
* @param options - Optional parsing configuration
* @returns Parsed JSON value, or `undefined` if parsing fails and `throws` is `false`
*
* @throws {SyntaxError} When JSON is invalid and `throws` is `true` (default)
*
* @example
* ```ts
* // Basic usage
* const data = jsonParse('{"name":"example"}')
* console.log(data.name) // => 'example'
*
* // Parse Buffer with UTF-8 BOM
* const buffer = Buffer.from('\uFEFF{"value":42}')
* const data = jsonParse(buffer)
* console.log(data.value) // => 42
*
* // Enhanced error messages with filepath
* try {
* jsonParse('invalid', { filepath: 'config.json' })
* } catch (err) {
* console.error(err.message)
* // => "config.json: Unexpected token i in JSON at position 0"
* }
*
* // Suppress errors
* const result = jsonParse('invalid', { throws: false })
* console.log(result) // => undefined
*
* // Transform values with reviver
* const json = '{"created":"2024-01-15T10:30:00Z"}'
* const data = jsonParse(json, {
* reviver: (key, value) => {
* if (key === 'created' && typeof value === 'string') {
* return new Date(value)
* }
* return value
* }
* })
* console.log(data.created instanceof Date) // => true
* ```
*/
/*@__NO_SIDE_EFFECTS__*/
export function jsonParse(
content: string | Buffer,
options?: JsonParseOptions | undefined,
): JsonValue | undefined {
const { filepath, reviver, throws } = {
__proto__: null,
...options,
} as JsonParseOptions
const shouldThrow = throws === undefined || !!throws
const jsonStr = isBuffer(content) ? content.toString('utf8') : content
try {
return JSONParse(stripBom(jsonStr), reviver)
} catch (e) {
if (shouldThrow) {
const error = e as Error
if (error && typeof filepath === 'string') {
error.message = `${filepath}: ${error.message}`
}
throw error
}
}
return undefined
}