tretool 0.2.1__py3-none-any.whl → 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tretool/__init__.py +39 -12
- tretool/config.py +406 -170
- tretool/decoratorlib.py +423 -0
- tretool/encoding.py +404 -75
- tretool/httplib.py +730 -0
- tretool/jsonlib.py +619 -151
- tretool/logger.py +712 -0
- tretool/mathlib.py +0 -33
- tretool/path.py +19 -0
- tretool/platformlib.py +469 -314
- tretool/plugin.py +437 -237
- tretool/smartCache.py +569 -0
- tretool/tasklib.py +730 -0
- tretool/transform/docx.py +544 -0
- tretool/transform/pdf.py +273 -95
- tretool/ziplib.py +664 -0
- {tretool-0.2.1.dist-info → tretool-1.0.0.dist-info}/METADATA +11 -5
- tretool-1.0.0.dist-info/RECORD +24 -0
- tretool/markfunc.py +0 -152
- tretool/memorizeTools.py +0 -24
- tretool/writeLog.py +0 -69
- tretool-0.2.1.dist-info/RECORD +0 -20
- {tretool-0.2.1.dist-info → tretool-1.0.0.dist-info}/WHEEL +0 -0
- {tretool-0.2.1.dist-info → tretool-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {tretool-0.2.1.dist-info → tretool-1.0.0.dist-info}/top_level.txt +0 -0
tretool/jsonlib.py
CHANGED
@@ -1,81 +1,146 @@
|
|
1
|
-
import
|
2
|
-
from typing import Any, Union, Dict, List, BinaryIO
|
1
|
+
import re
|
2
|
+
from typing import Any, Union, Dict, List, Callable, BinaryIO, TextIO, Optional, Tuple
|
3
|
+
from datetime import datetime, date, time
|
4
|
+
from decimal import Decimal
|
5
|
+
from enum import Enum
|
6
|
+
from pathlib import Path
|
7
|
+
import base64
|
8
|
+
import uuid
|
3
9
|
|
4
|
-
class
|
10
|
+
class JSONError(Exception):
|
11
|
+
"""JSON 处理基类异常"""
|
12
|
+
pass
|
13
|
+
|
14
|
+
class JSONEncodeError(JSONError):
|
5
15
|
"""JSON 编码异常"""
|
6
16
|
pass
|
7
17
|
|
8
|
-
class JSONDecodeError(
|
18
|
+
class JSONDecodeError(JSONError):
|
9
19
|
"""JSON 解码异常"""
|
10
20
|
pass
|
11
21
|
|
12
|
-
|
22
|
+
class JSONEncoder:
|
13
23
|
"""
|
14
|
-
|
15
|
-
|
16
|
-
参数:
|
17
|
-
obj: 要序列化的 Python 对象
|
18
|
-
indent: 缩进空格数(None 表示紧凑格式)
|
24
|
+
增强版 JSON 编码器
|
19
25
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
26
|
+
特性:
|
27
|
+
- 支持常见 Python 类型的序列化
|
28
|
+
- 可扩展的自定义类型处理
|
29
|
+
- 格式化输出控制
|
30
|
+
- 循环引用检测
|
31
|
+
- 特殊字符转义
|
25
32
|
"""
|
26
|
-
if indent is not None and not isinstance(indent, int):
|
27
|
-
raise TypeError("indent must be int or None")
|
28
33
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
34
|
+
def __init__(
|
35
|
+
self,
|
36
|
+
*,
|
37
|
+
indent: Optional[int] = None,
|
38
|
+
ensure_ascii: bool = True,
|
39
|
+
sort_keys: bool = False,
|
40
|
+
skipkeys: bool = False,
|
41
|
+
allow_nan: bool = True,
|
42
|
+
default: Optional[Callable[[Any], Any]] = None,
|
43
|
+
use_decimal: bool = False,
|
44
|
+
datetime_format: str = '%Y-%m-%d %H:%M:%S',
|
45
|
+
date_format: str = '%Y-%m-%d',
|
46
|
+
time_format: str = '%H:%M:%S'
|
47
|
+
):
|
48
|
+
"""
|
49
|
+
初始化编码器
|
50
|
+
|
51
|
+
参数:
|
52
|
+
indent: 缩进空格数
|
53
|
+
ensure_ascii: 是否确保 ASCII 输出
|
54
|
+
sort_keys: 是否按键排序
|
55
|
+
skipkeys: 是否跳过非字符串键
|
56
|
+
allow_nan: 是否允许 NaN/Infinity
|
57
|
+
default: 默认转换函数
|
58
|
+
use_decimal: 是否使用 Decimal 处理浮点数
|
59
|
+
datetime_format: 日期时间格式
|
60
|
+
date_format: 日期格式
|
61
|
+
time_format: 时间格式
|
62
|
+
"""
|
51
63
|
self.indent = indent
|
64
|
+
self.ensure_ascii = ensure_ascii
|
65
|
+
self.sort_keys = sort_keys
|
66
|
+
self.skipkeys = skipkeys
|
67
|
+
self.allow_nan = allow_nan
|
68
|
+
self.default = default
|
69
|
+
self.use_decimal = use_decimal
|
70
|
+
self.datetime_format = datetime_format
|
71
|
+
self.date_format = date_format
|
72
|
+
self.time_format = time_format
|
52
73
|
self._current_indent = 0
|
74
|
+
self._memo = set()
|
53
75
|
|
54
76
|
def encode(self, obj: Any) -> str:
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
77
|
+
"""编码 Python 对象为 JSON 字符串"""
|
78
|
+
self._memo.clear()
|
79
|
+
return self._encode(obj)
|
80
|
+
|
81
|
+
def _encode(self, obj: Any) -> str:
|
82
|
+
"""内部编码方法"""
|
83
|
+
obj_id = id(obj)
|
84
|
+
|
85
|
+
# 检查循环引用
|
86
|
+
if obj_id in self._memo:
|
87
|
+
raise JSONEncodeError("Circular reference detected")
|
88
|
+
self._memo.add(obj_id)
|
89
|
+
|
90
|
+
try:
|
91
|
+
if obj is None:
|
92
|
+
return "null"
|
93
|
+
elif isinstance(obj, bool):
|
94
|
+
return "true" if obj else "false"
|
95
|
+
elif isinstance(obj, (int, float)):
|
96
|
+
return self._encode_number(obj)
|
97
|
+
elif isinstance(obj, str):
|
98
|
+
return self._encode_string(obj)
|
99
|
+
elif isinstance(obj, (list, tuple, set, frozenset)):
|
100
|
+
return self._encode_array(obj)
|
101
|
+
elif isinstance(obj, dict):
|
102
|
+
return self._encode_object(obj)
|
103
|
+
elif isinstance(obj, (datetime, date, time)):
|
104
|
+
return self._encode_datetime(obj)
|
105
|
+
elif isinstance(obj, Decimal):
|
106
|
+
return self._encode_decimal(obj)
|
107
|
+
elif isinstance(obj, (bytes, bytearray)):
|
108
|
+
return self._encode_bytes(obj)
|
109
|
+
elif isinstance(obj, Enum):
|
110
|
+
return self._encode(obj.value)
|
111
|
+
elif isinstance(obj, (Path, uuid.UUID)):
|
112
|
+
return self._encode_string(str(obj))
|
113
|
+
elif self.default is not None:
|
114
|
+
return self._encode(self.default(obj))
|
115
|
+
else:
|
116
|
+
raise JSONEncodeError(f"Object of type {type(obj)} is not JSON serializable")
|
117
|
+
finally:
|
118
|
+
self._memo.discard(obj_id)
|
69
119
|
|
70
120
|
def _encode_number(self, num: Union[int, float]) -> str:
|
121
|
+
"""编码数字"""
|
71
122
|
if isinstance(num, int):
|
72
123
|
return str(num)
|
73
|
-
|
124
|
+
|
125
|
+
if not self.allow_nan:
|
126
|
+
if num != num: # NaN
|
127
|
+
raise JSONEncodeError("NaN is not allowed")
|
128
|
+
if num in (float('inf'), float('-inf')):
|
129
|
+
raise JSONEncodeError("Infinity is not allowed")
|
130
|
+
|
131
|
+
if self.use_decimal and isinstance(num, float):
|
132
|
+
return str(Decimal.from_float(num).normalize())
|
133
|
+
|
134
|
+
if num.is_integer():
|
74
135
|
return str(int(num))
|
75
|
-
|
76
|
-
|
136
|
+
|
137
|
+
return str(num)
|
77
138
|
|
78
139
|
def _encode_string(self, s: str) -> str:
|
140
|
+
"""编码字符串"""
|
141
|
+
if not self.ensure_ascii:
|
142
|
+
return f'"{s}"'
|
143
|
+
|
79
144
|
escape_map = {
|
80
145
|
'\"': '\\"',
|
81
146
|
'\\': '\\\\',
|
@@ -92,53 +157,196 @@ class _Encoder:
|
|
92
157
|
result.append(escape_map[char])
|
93
158
|
elif ord(char) < 0x20:
|
94
159
|
result.append(f"\\u{ord(char):04x}")
|
160
|
+
elif ord(char) > 0x7f:
|
161
|
+
result.append(f"\\u{ord(char):04x}")
|
95
162
|
else:
|
96
163
|
result.append(char)
|
97
164
|
|
98
165
|
return f'"{"".join(result)}"'
|
99
166
|
|
100
|
-
def _encode_array(self, array: List[Any]) -> str:
|
167
|
+
def _encode_array(self, array: Union[List[Any], Tuple[Any, ...], set, frozenset]) -> str:
|
168
|
+
"""编码数组"""
|
101
169
|
if not array:
|
102
170
|
return "[]"
|
103
171
|
|
104
172
|
if self.indent is None:
|
105
|
-
items = [self.
|
173
|
+
items = [self._encode(item) for item in array]
|
106
174
|
return f"[{','.join(items)}]"
|
107
175
|
else:
|
108
176
|
self._current_indent += self.indent
|
109
177
|
indent_str = "\n" + " " * self._current_indent
|
110
|
-
items = [f"{indent_str}{self.
|
178
|
+
items = [f"{indent_str}{self._encode(item)}" for item in array]
|
111
179
|
self._current_indent -= self.indent
|
112
180
|
return f"[{','.join(items)}\n{' ' * self._current_indent}]"
|
113
181
|
|
114
182
|
def _encode_object(self, obj: Dict[str, Any]) -> str:
|
183
|
+
"""编码对象"""
|
115
184
|
if not obj:
|
116
185
|
return "{}"
|
117
186
|
|
187
|
+
if self.sort_keys:
|
188
|
+
items = sorted(obj.items(), key=lambda x: x[0])
|
189
|
+
else:
|
190
|
+
items = obj.items()
|
191
|
+
|
118
192
|
if self.indent is None:
|
119
|
-
|
120
|
-
|
193
|
+
pairs = []
|
194
|
+
for k, v in items:
|
195
|
+
if not isinstance(k, str):
|
196
|
+
if self.skipkeys:
|
197
|
+
continue
|
198
|
+
raise JSONEncodeError("Keys must be strings")
|
199
|
+
pairs.append(f"{self._encode_string(k)}:{self._encode(v)}")
|
200
|
+
return f"{{{','.join(pairs)}}}"
|
121
201
|
else:
|
122
202
|
self._current_indent += self.indent
|
123
203
|
indent_str = "\n" + " " * self._current_indent
|
124
|
-
|
125
|
-
for k, v in
|
126
|
-
|
127
|
-
|
128
|
-
|
204
|
+
pairs = []
|
205
|
+
for k, v in items:
|
206
|
+
if not isinstance(k, str):
|
207
|
+
if self.skipkeys:
|
208
|
+
continue
|
209
|
+
raise JSONEncodeError("Keys must be strings")
|
210
|
+
key_str = self._encode_string(k)
|
211
|
+
value_str = self._encode(v)
|
212
|
+
pairs.append(f"{indent_str}{key_str}: {value_str}")
|
129
213
|
self._current_indent -= self.indent
|
130
|
-
return f"{{{','.join(
|
214
|
+
return f"{{{','.join(pairs)}\n{' ' * self._current_indent}}}"
|
215
|
+
|
216
|
+
def _encode_datetime(self, dt: Union[datetime, date, time]) -> str:
|
217
|
+
"""编码日期时间"""
|
218
|
+
if isinstance(dt, datetime):
|
219
|
+
return self._encode_string(dt.strftime(self.datetime_format))
|
220
|
+
elif isinstance(dt, date):
|
221
|
+
return self._encode_string(dt.strftime(self.date_format))
|
222
|
+
elif isinstance(dt, time):
|
223
|
+
return self._encode_string(dt.strftime(self.time_format))
|
224
|
+
|
225
|
+
def _encode_decimal(self, dec: Decimal) -> str:
|
226
|
+
"""编码 Decimal"""
|
227
|
+
if self.use_decimal:
|
228
|
+
return str(dec.normalize())
|
229
|
+
return str(float(dec))
|
230
|
+
|
231
|
+
def _encode_bytes(self, b: Union[bytes, bytearray]) -> str:
|
232
|
+
"""编码字节数据"""
|
233
|
+
return self._encode_string(base64.b64encode(b).decode('ascii'))
|
234
|
+
|
235
|
+
class JSONDecoder:
|
236
|
+
"""
|
237
|
+
增强版 JSON 解码器
|
238
|
+
|
239
|
+
特性:
|
240
|
+
- 严格的 JSON 语法验证
|
241
|
+
- 支持自定义对象钩子
|
242
|
+
- 日期时间自动解析
|
243
|
+
- 错误位置报告
|
244
|
+
"""
|
245
|
+
|
246
|
+
def __init__(
|
247
|
+
self,
|
248
|
+
*,
|
249
|
+
object_hook: Optional[Callable[[Dict[str, Any]], Any]] = None,
|
250
|
+
parse_float: Optional[Callable[[str], Any]] = None,
|
251
|
+
parse_int: Optional[Callable[[str], Any]] = None,
|
252
|
+
parse_constant: Optional[Callable[[str], Any]] = None,
|
253
|
+
datetime_format: str = '%Y-%m-%d %H:%M:%S',
|
254
|
+
date_format: str = '%Y-%m-%d',
|
255
|
+
time_format: str = '%H:%M:%S',
|
256
|
+
strict: bool = True
|
257
|
+
):
|
258
|
+
"""
|
259
|
+
初始化解码器
|
260
|
+
|
261
|
+
参数:
|
262
|
+
object_hook: 对象解析钩子
|
263
|
+
parse_float: 浮点数解析函数
|
264
|
+
parse_int: 整数解析函数
|
265
|
+
parse_constant: 常量解析函数
|
266
|
+
datetime_format: 日期时间格式
|
267
|
+
date_format: 日期格式
|
268
|
+
time_format: 时间格式
|
269
|
+
strict: 是否严格模式
|
270
|
+
"""
|
271
|
+
self.object_hook = object_hook
|
272
|
+
self.parse_float = parse_float or float
|
273
|
+
self.parse_int = parse_int or int
|
274
|
+
self.parse_constant = parse_constant or (lambda x: None)
|
275
|
+
self.datetime_format = datetime_format
|
276
|
+
self.date_format = date_format
|
277
|
+
self.time_format = time_format
|
278
|
+
self.strict = strict
|
279
|
+
|
280
|
+
def decode(self, json_str: str) -> Any:
|
281
|
+
"""解码 JSON 字符串为 Python 对象"""
|
282
|
+
if not isinstance(json_str, str):
|
283
|
+
raise JSONDecodeError("Input must be a string")
|
284
|
+
|
285
|
+
parser = _JSONParser(
|
286
|
+
json_str,
|
287
|
+
object_hook=self.object_hook,
|
288
|
+
parse_float=self.parse_float,
|
289
|
+
parse_int=self.parse_int,
|
290
|
+
parse_constant=self.parse_constant,
|
291
|
+
datetime_format=self.datetime_format,
|
292
|
+
date_format=self.date_format,
|
293
|
+
time_format=self.time_format,
|
294
|
+
strict=self.strict
|
295
|
+
)
|
296
|
+
return parser.parse()
|
131
297
|
|
132
|
-
class
|
298
|
+
class _JSONParser:
|
133
299
|
"""JSON 解析器实现"""
|
134
300
|
|
135
|
-
|
136
|
-
|
301
|
+
WHITESPACE = re.compile(r'[\s\n\r\t]*')
|
302
|
+
NUMBER_RE = re.compile(
|
303
|
+
r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?',
|
304
|
+
re.ASCII
|
305
|
+
)
|
306
|
+
|
307
|
+
def __init__(
|
308
|
+
self,
|
309
|
+
json_str: str,
|
310
|
+
*,
|
311
|
+
object_hook: Optional[Callable[[Dict[str, Any]], Any]] = None,
|
312
|
+
parse_float: Optional[Callable[[str], Any]] = None,
|
313
|
+
parse_int: Optional[Callable[[str], Any]] = None,
|
314
|
+
parse_constant: Optional[Callable[[str], Any]] = None,
|
315
|
+
datetime_format: str = '%Y-%m-%d %H:%M:%S',
|
316
|
+
date_format: str = '%Y-%m-%d',
|
317
|
+
time_format: str = '%H:%M:%S',
|
318
|
+
strict: bool = True
|
319
|
+
):
|
320
|
+
self.json_str = json_str
|
137
321
|
self.idx = 0
|
138
322
|
self.len = len(json_str)
|
323
|
+
self.object_hook = object_hook
|
324
|
+
self.parse_float = parse_float
|
325
|
+
self.parse_int = parse_int
|
326
|
+
self.parse_constant = parse_constant
|
327
|
+
self.datetime_format = datetime_format
|
328
|
+
self.date_format = date_format
|
329
|
+
self.time_format = time_format
|
330
|
+
self.strict = strict
|
139
331
|
|
140
332
|
def parse(self) -> Any:
|
141
|
-
|
333
|
+
"""解析 JSON 字符串"""
|
334
|
+
self._skip_whitespace()
|
335
|
+
value = self._parse_value()
|
336
|
+
self._skip_whitespace()
|
337
|
+
|
338
|
+
if self.idx != self.len:
|
339
|
+
raise JSONDecodeError(
|
340
|
+
f"Extra data at position {self.idx}",
|
341
|
+
self.json_str,
|
342
|
+
self.idx
|
343
|
+
)
|
344
|
+
|
345
|
+
return value
|
346
|
+
|
347
|
+
def _parse_value(self) -> Any:
|
348
|
+
"""解析 JSON 值"""
|
349
|
+
char = self.json_str[self.idx]
|
142
350
|
|
143
351
|
if char == '{':
|
144
352
|
return self._parse_object()
|
@@ -146,75 +354,104 @@ class _Parser:
|
|
146
354
|
return self._parse_array()
|
147
355
|
elif char == '"':
|
148
356
|
return self._parse_string()
|
149
|
-
elif char == 'n' and self.
|
150
|
-
self.idx += 4
|
357
|
+
elif char == 'n' and self._match('null'):
|
151
358
|
return None
|
152
|
-
elif char == 't' and self.
|
153
|
-
self.idx += 4
|
359
|
+
elif char == 't' and self._match('true'):
|
154
360
|
return True
|
155
|
-
elif char == 'f' and self.
|
156
|
-
self.idx += 5
|
361
|
+
elif char == 'f' and self._match('false'):
|
157
362
|
return False
|
158
363
|
elif char == '-' or char.isdigit():
|
159
364
|
return self._parse_number()
|
365
|
+
elif char == 'N' and self._match('NaN'):
|
366
|
+
return self.parse_constant('NaN')
|
367
|
+
elif char == 'I' and self._match('Infinity'):
|
368
|
+
return self.parse_constant('Infinity')
|
369
|
+
elif char == '-' and self._match('-Infinity'):
|
370
|
+
return self.parse_constant('-Infinity')
|
160
371
|
else:
|
161
|
-
raise JSONDecodeError(
|
372
|
+
raise JSONDecodeError(
|
373
|
+
f"Unexpected character at position {self.idx}: {char}",
|
374
|
+
self.json_str,
|
375
|
+
self.idx
|
376
|
+
)
|
162
377
|
|
163
378
|
def _parse_object(self) -> Dict[str, Any]:
|
379
|
+
"""解析 JSON 对象"""
|
164
380
|
obj = {}
|
165
381
|
self._consume('{')
|
382
|
+
self._skip_whitespace()
|
166
383
|
|
167
|
-
|
384
|
+
if self.json_str[self.idx] == '}':
|
385
|
+
self._consume('}')
|
386
|
+
return obj
|
387
|
+
|
388
|
+
while True:
|
168
389
|
# 解析键
|
169
390
|
key = self._parse_string()
|
391
|
+
self._skip_whitespace()
|
170
392
|
self._consume(':')
|
393
|
+
self._skip_whitespace()
|
171
394
|
|
172
395
|
# 解析值
|
173
|
-
value = self.
|
396
|
+
value = self._parse_value()
|
174
397
|
obj[key] = value
|
175
398
|
|
176
|
-
|
177
|
-
if self.
|
178
|
-
self._consume('
|
179
|
-
|
180
|
-
|
399
|
+
self._skip_whitespace()
|
400
|
+
if self.json_str[self.idx] == '}':
|
401
|
+
self._consume('}')
|
402
|
+
break
|
403
|
+
self._consume(',')
|
404
|
+
self._skip_whitespace()
|
181
405
|
|
182
|
-
self.
|
406
|
+
if self.object_hook is not None:
|
407
|
+
return self.object_hook(obj)
|
183
408
|
return obj
|
184
409
|
|
185
410
|
def _parse_array(self) -> List[Any]:
|
411
|
+
"""解析 JSON 数组"""
|
186
412
|
arr = []
|
187
413
|
self._consume('[')
|
414
|
+
self._skip_whitespace()
|
415
|
+
|
416
|
+
if self.json_str[self.idx] == ']':
|
417
|
+
self._consume(']')
|
418
|
+
return arr
|
188
419
|
|
189
|
-
while
|
420
|
+
while True:
|
190
421
|
# 解析元素
|
191
|
-
arr.append(self.
|
422
|
+
arr.append(self._parse_value())
|
423
|
+
self._skip_whitespace()
|
192
424
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
425
|
+
if self.json_str[self.idx] == ']':
|
426
|
+
self._consume(']')
|
427
|
+
break
|
428
|
+
self._consume(',')
|
429
|
+
self._skip_whitespace()
|
198
430
|
|
199
|
-
self._consume(']')
|
200
431
|
return arr
|
201
432
|
|
202
433
|
def _parse_string(self) -> str:
|
434
|
+
"""解析 JSON 字符串"""
|
203
435
|
self._consume('"')
|
204
436
|
chars = []
|
205
437
|
|
206
|
-
while self.
|
207
|
-
char = self.
|
438
|
+
while self.json_str[self.idx] != '"':
|
439
|
+
char = self.json_str[self.idx]
|
208
440
|
|
209
441
|
if char == '\\':
|
210
442
|
self._consume('\\')
|
211
|
-
esc_char = self.
|
443
|
+
esc_char = self.json_str[self.idx]
|
444
|
+
|
212
445
|
if esc_char == 'u':
|
213
446
|
# Unicode 转义
|
214
447
|
self._consume('u')
|
215
448
|
hex_str = self.json_str[self.idx:self.idx+4]
|
216
449
|
if len(hex_str) != 4:
|
217
|
-
raise JSONDecodeError(
|
450
|
+
raise JSONDecodeError(
|
451
|
+
"Invalid Unicode escape sequence",
|
452
|
+
self.json_str,
|
453
|
+
self.idx
|
454
|
+
)
|
218
455
|
self.idx += 4
|
219
456
|
chars.append(chr(int(hex_str, 16)))
|
220
457
|
else:
|
@@ -232,68 +469,299 @@ class _Parser:
|
|
232
469
|
chars.append(escape_map.get(esc_char, esc_char))
|
233
470
|
self._consume(esc_char)
|
234
471
|
else:
|
472
|
+
if ord(char) < 0x20 and self.strict:
|
473
|
+
raise JSONDecodeError(
|
474
|
+
"Invalid control character in string",
|
475
|
+
self.json_str,
|
476
|
+
self.idx
|
477
|
+
)
|
235
478
|
chars.append(char)
|
236
479
|
self._consume(char)
|
237
480
|
|
238
481
|
self._consume('"')
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
start_idx = self.idx
|
243
|
-
is_float = False
|
244
|
-
|
245
|
-
# 处理符号
|
246
|
-
if self._peek() == '-':
|
247
|
-
self._consume('-')
|
248
|
-
|
249
|
-
# 整数部分
|
250
|
-
while self._peek().isdigit():
|
251
|
-
self._consume()
|
252
|
-
|
253
|
-
# 小数部分
|
254
|
-
if self._peek() == '.':
|
255
|
-
is_float = True
|
256
|
-
self._consume('.')
|
257
|
-
while self._peek().isdigit():
|
258
|
-
self._consume()
|
259
|
-
|
260
|
-
# 指数部分
|
261
|
-
if self._peek().lower() == 'e':
|
262
|
-
is_float = True
|
263
|
-
self._consume()
|
264
|
-
if self._peek() in ('+', '-'):
|
265
|
-
self._consume()
|
266
|
-
while self._peek().isdigit():
|
267
|
-
self._consume()
|
268
|
-
|
269
|
-
num_str = self.json_str[start_idx:self.idx]
|
482
|
+
s = ''.join(chars)
|
483
|
+
|
484
|
+
# 尝试解析日期时间
|
270
485
|
try:
|
271
|
-
|
486
|
+
if len(s) == len(self.datetime_format):
|
487
|
+
return datetime.strptime(s, self.datetime_format)
|
488
|
+
elif len(s) == len(self.date_format):
|
489
|
+
return datetime.strptime(s, self.date_format).date()
|
490
|
+
elif len(s) == len(self.time_format):
|
491
|
+
return datetime.strptime(s, self.time_format).time()
|
272
492
|
except ValueError:
|
273
|
-
|
493
|
+
pass
|
494
|
+
|
495
|
+
return s
|
274
496
|
|
275
|
-
def
|
276
|
-
|
277
|
-
|
278
|
-
|
497
|
+
def _parse_number(self) -> Union[int, float]:
|
498
|
+
"""解析 JSON 数字"""
|
499
|
+
match = self.NUMBER_RE.match(self.json_str, self.idx)
|
500
|
+
if not match:
|
501
|
+
raise JSONDecodeError(
|
502
|
+
"Invalid number literal",
|
503
|
+
self.json_str,
|
504
|
+
self.idx
|
505
|
+
)
|
506
|
+
|
507
|
+
integer, fraction, exponent = match.groups()
|
508
|
+
self.idx = match.end()
|
509
|
+
|
510
|
+
if fraction or exponent:
|
511
|
+
num_str = integer + (fraction or '') + (exponent or '')
|
512
|
+
return self.parse_float(num_str)
|
513
|
+
else:
|
514
|
+
return self.parse_int(integer)
|
279
515
|
|
280
|
-
def
|
281
|
-
|
282
|
-
|
283
|
-
|
516
|
+
def _skip_whitespace(self) -> None:
|
517
|
+
"""跳过空白字符"""
|
518
|
+
while self.idx < self.len and self.json_str[self.idx] in ' \t\n\r':
|
519
|
+
self.idx += 1
|
284
520
|
|
285
|
-
def _consume(self, expected: str
|
286
|
-
|
287
|
-
|
521
|
+
def _consume(self, expected: str) -> None:
|
522
|
+
"""消费指定字符"""
|
523
|
+
if self.idx >= self.len or self.json_str[self.idx] != expected:
|
524
|
+
raise JSONDecodeError(
|
525
|
+
f"Expected '{expected}' at position {self.idx}",
|
526
|
+
self.json_str,
|
527
|
+
self.idx
|
528
|
+
)
|
288
529
|
self.idx += 1
|
530
|
+
|
531
|
+
def _match(self, literal: str) -> bool:
|
532
|
+
"""匹配指定字符串"""
|
533
|
+
if self.json_str.startswith(literal, self.idx):
|
534
|
+
self.idx += len(literal)
|
535
|
+
return True
|
536
|
+
return False
|
537
|
+
|
538
|
+
# 高级接口函数
|
539
|
+
def dumps(
|
540
|
+
obj: Any,
|
541
|
+
*,
|
542
|
+
indent: Optional[int] = None,
|
543
|
+
ensure_ascii: bool = True,
|
544
|
+
sort_keys: bool = False,
|
545
|
+
skipkeys: bool = False,
|
546
|
+
allow_nan: bool = True,
|
547
|
+
default: Optional[Callable[[Any], Any]] = None,
|
548
|
+
use_decimal: bool = False,
|
549
|
+
**kwargs
|
550
|
+
) -> str:
|
551
|
+
"""
|
552
|
+
将 Python 对象序列化为 JSON 字符串
|
553
|
+
|
554
|
+
参数:
|
555
|
+
obj: 要序列化的对象
|
556
|
+
indent: 缩进空格数
|
557
|
+
ensure_ascii: 是否确保 ASCII 输出
|
558
|
+
sort_keys: 是否按键排序
|
559
|
+
skipkeys: 是否跳过非字符串键
|
560
|
+
allow_nan: 是否允许 NaN/Infinity
|
561
|
+
default: 默认转换函数
|
562
|
+
use_decimal: 是否使用 Decimal 处理浮点数
|
563
|
+
kwargs: 其他编码器参数
|
564
|
+
|
565
|
+
返回:
|
566
|
+
JSON 字符串
|
567
|
+
"""
|
568
|
+
encoder = JSONEncoder(
|
569
|
+
indent=indent,
|
570
|
+
ensure_ascii=ensure_ascii,
|
571
|
+
sort_keys=sort_keys,
|
572
|
+
skipkeys=skipkeys,
|
573
|
+
allow_nan=allow_nan,
|
574
|
+
default=default,
|
575
|
+
use_decimal=use_decimal,
|
576
|
+
**kwargs
|
577
|
+
)
|
578
|
+
return encoder.encode(obj)
|
579
|
+
|
580
|
+
def loads(
|
581
|
+
json_str: str,
|
582
|
+
*,
|
583
|
+
object_hook: Optional[Callable[[Dict[str, Any]], Any]] = None,
|
584
|
+
parse_float: Optional[Callable[[str], Any]] = None,
|
585
|
+
parse_int: Optional[Callable[[str], Any]] = None,
|
586
|
+
parse_constant: Optional[Callable[[str], Any]] = None,
|
587
|
+
strict: bool = True,
|
588
|
+
**kwargs
|
589
|
+
) -> Any:
|
590
|
+
"""
|
591
|
+
将 JSON 字符串解析为 Python 对象
|
592
|
+
|
593
|
+
参数:
|
594
|
+
json_str: JSON 字符串
|
595
|
+
object_hook: 对象解析钩子
|
596
|
+
parse_float: 浮点数解析函数
|
597
|
+
parse_int: 整数解析函数
|
598
|
+
parse_constant: 常量解析函数
|
599
|
+
strict: 是否严格模式
|
600
|
+
kwargs: 其他解码器参数
|
601
|
+
|
602
|
+
返回:
|
603
|
+
Python 对象
|
604
|
+
"""
|
605
|
+
decoder = JSONDecoder(
|
606
|
+
object_hook=object_hook,
|
607
|
+
parse_float=parse_float,
|
608
|
+
parse_int=parse_int,
|
609
|
+
parse_constant=parse_constant,
|
610
|
+
strict=strict,
|
611
|
+
**kwargs
|
612
|
+
)
|
613
|
+
return decoder.decode(json_str)
|
289
614
|
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
615
|
+
def dump(
|
616
|
+
obj: Any,
|
617
|
+
file: TextIO,
|
618
|
+
*,
|
619
|
+
indent: Optional[int] = None,
|
620
|
+
ensure_ascii: bool = True,
|
621
|
+
sort_keys: bool = False,
|
622
|
+
skipkeys: bool = False,
|
623
|
+
allow_nan: bool = True,
|
624
|
+
default: Optional[Callable[[Any], Any]] = None,
|
625
|
+
use_decimal: bool = False,
|
626
|
+
**kwargs
|
627
|
+
) -> None:
|
628
|
+
"""
|
629
|
+
将 Python 对象序列化为 JSON 并写入文件
|
630
|
+
|
631
|
+
参数:
|
632
|
+
obj: 要序列化的对象
|
633
|
+
file: 文件对象
|
634
|
+
indent: 缩进空格数
|
635
|
+
ensure_ascii: 是否确保 ASCII 输出
|
636
|
+
sort_keys: 是否按键排序
|
637
|
+
skipkeys: 是否跳过非字符串键
|
638
|
+
allow_nan: 是否允许 NaN/Infinity
|
639
|
+
default: 默认转换函数
|
640
|
+
use_decimal: 是否使用 Decimal 处理浮点数
|
641
|
+
kwargs: 其他编码器参数
|
642
|
+
"""
|
643
|
+
json_str = dumps(
|
644
|
+
obj,
|
645
|
+
indent=indent,
|
646
|
+
ensure_ascii=ensure_ascii,
|
647
|
+
sort_keys=sort_keys,
|
648
|
+
skipkeys=skipkeys,
|
649
|
+
allow_nan=allow_nan,
|
650
|
+
default=default,
|
651
|
+
use_decimal=use_decimal,
|
652
|
+
**kwargs
|
653
|
+
)
|
654
|
+
file.write(json_str)
|
295
655
|
|
296
|
-
def
|
297
|
-
|
298
|
-
|
299
|
-
|
656
|
+
def load(
|
657
|
+
file: TextIO,
|
658
|
+
*,
|
659
|
+
object_hook: Optional[Callable[[Dict[str, Any]], Any]] = None,
|
660
|
+
parse_float: Optional[Callable[[str], Any]] = None,
|
661
|
+
parse_int: Optional[Callable[[str], Any]] = None,
|
662
|
+
parse_constant: Optional[Callable[[str], Any]] = None,
|
663
|
+
strict: bool = True,
|
664
|
+
**kwargs
|
665
|
+
) -> Any:
|
666
|
+
"""
|
667
|
+
从文件读取 JSON 数据并解析为 Python 对象
|
668
|
+
|
669
|
+
参数:
|
670
|
+
fp: 文件对象
|
671
|
+
object_hook: 对象解析钩子
|
672
|
+
parse_float: 浮点数解析函数
|
673
|
+
parse_int: 整数解析函数
|
674
|
+
parse_constant: 常量解析函数
|
675
|
+
strict: 是否严格模式
|
676
|
+
kwargs: 其他解码器参数
|
677
|
+
|
678
|
+
返回:
|
679
|
+
Python 对象
|
680
|
+
"""
|
681
|
+
return loads(file.read(), object_hook=object_hook, parse_float=parse_float,
|
682
|
+
parse_int=parse_int, parse_constant=parse_constant,
|
683
|
+
strict=strict, **kwargs)
|
684
|
+
|
685
|
+
# 便捷函数
|
686
|
+
# 便捷函数
|
687
|
+
def dump_to_file(
|
688
|
+
obj: Any,
|
689
|
+
filepath: Union[str, Path],
|
690
|
+
encoding: str = 'utf-8',
|
691
|
+
*,
|
692
|
+
indent: Optional[int] = None,
|
693
|
+
ensure_ascii: bool = True,
|
694
|
+
sort_keys: bool = False,
|
695
|
+
skipkeys: bool = False,
|
696
|
+
allow_nan: bool = True,
|
697
|
+
default: Optional[Callable[[Any], Any]] = None,
|
698
|
+
use_decimal: bool = False,
|
699
|
+
**kwargs
|
700
|
+
) -> None:
|
701
|
+
"""
|
702
|
+
将 Python 对象序列化为 JSON 并写入文件
|
703
|
+
|
704
|
+
参数:
|
705
|
+
obj: 要序列化的对象
|
706
|
+
filepath: 文件路径
|
707
|
+
encoding: 文件编码
|
708
|
+
indent: 缩进空格数
|
709
|
+
ensure_ascii: 是否确保 ASCII 输出
|
710
|
+
sort_keys: 是否按键排序
|
711
|
+
skipkeys: 是否跳过非字符串键
|
712
|
+
allow_nan: 是否允许 NaN/Infinity
|
713
|
+
default: 默认转换函数
|
714
|
+
use_decimal: 是否使用 Decimal 处理浮点数
|
715
|
+
kwargs: 其他编码器参数
|
716
|
+
"""
|
717
|
+
with open(filepath, 'w', encoding=encoding) as f:
|
718
|
+
dump(
|
719
|
+
obj,
|
720
|
+
f, # 添加文件对象参数
|
721
|
+
indent=indent,
|
722
|
+
ensure_ascii=ensure_ascii,
|
723
|
+
sort_keys=sort_keys,
|
724
|
+
skipkeys=skipkeys,
|
725
|
+
allow_nan=allow_nan,
|
726
|
+
default=default,
|
727
|
+
use_decimal=use_decimal,
|
728
|
+
**kwargs
|
729
|
+
)
|
730
|
+
|
731
|
+
def load_from_file(
|
732
|
+
filepath: Union[str, Path],
|
733
|
+
encoding: str = 'utf-8',
|
734
|
+
*,
|
735
|
+
object_hook: Optional[Callable[[Dict[str, Any]], Any]] = None,
|
736
|
+
parse_float: Optional[Callable[[str], Any]] = None,
|
737
|
+
parse_int: Optional[Callable[[str], Any]] = None,
|
738
|
+
parse_constant: Optional[Callable[[str], Any]] = None,
|
739
|
+
strict: bool = True,
|
740
|
+
**kwargs
|
741
|
+
) -> Any:
|
742
|
+
"""
|
743
|
+
从文件读取 JSON 数据并解析为 Python 对象
|
744
|
+
|
745
|
+
参数:
|
746
|
+
filepath: 文件路径
|
747
|
+
encoding: 文件编码
|
748
|
+
object_hook: 对象解析钩子
|
749
|
+
parse_float: 浮点数解析函数
|
750
|
+
parse_int: 整数解析函数
|
751
|
+
parse_constant: 常量解析函数
|
752
|
+
strict: 是否严格模式
|
753
|
+
kwargs: 其他解码器参数
|
754
|
+
|
755
|
+
返回:
|
756
|
+
Python 对象
|
757
|
+
"""
|
758
|
+
with open(filepath, 'r', encoding=encoding) as f:
|
759
|
+
return load(
|
760
|
+
f,
|
761
|
+
object_hook=object_hook,
|
762
|
+
parse_float=parse_float,
|
763
|
+
parse_int=parse_int,
|
764
|
+
parse_constant=parse_constant,
|
765
|
+
strict=strict,
|
766
|
+
**kwargs
|
767
|
+
)
|