该文件实现了一个自定义的 JSON 解码器,扩展了 Python 标准库的 json.JSONDecoder。核心功能是支持单引号字符串和三引号字符串(''' 和 """)作为有效的 JSON 字符串分隔符,同时保持了与标准 JSON 解析的兼容性。它通过重写扫描器(scan_once)、字符串解析(scanstring)和对象解析(JSONObject)等底层函数来实现这些扩展。
graph TD
A[调用 CustomDecoder.decode(s)] --> B[调用父类 JSONDecoder.decode]
B --> C[内部调用 scan_once 进行词法分析]
C --> D{识别下一个字符}
D -- '"' 或 "'" --> E[调用 parse_string (py_scanstring)]
D -- '{' --> F[调用 parse_object (JSONObject)]
D -- '[' --> G[调用 parse_array]
D -- 'n', 't', 'f' --> H[解析 null, true, false]
D -- 数字 --> I[调用 match_number 解析数字]
D -- 'N', 'I', '-' --> J[解析 NaN, Infinity]
E --> K{是否为三引号?}
K -- 是 --> L[使用三引号正则匹配块]
K -- 否 --> M[使用单/双引号正则匹配块]
L & M --> N[循环: 匹配内容与终止符]
N --> O{终止符类型?}
O -- 引号 --> P[字符串结束]
O -- 反斜杠 \\ --> Q[处理转义序列]
O -- 控制字符 --> R{strict 模式?}
R -- 是 --> S[抛出 JSONDecodeError]
R -- 否 --> T[作为字面量追加]
Q --> U{是 \\u 转义?}
U -- 是 --> V[调用 _decode_uXXXX 解码 Unicode]
U -- 否 --> W[从 BACKSLASH 表查找]
F --> X[解析键值对]
X --> Y[使用 scan_once 解析值]
Y --> Z[收集到 pairs 列表]
Z --> AA{遇到 '}' ?}
AA -- 否 --> AB{遇到 ',' ?}
AB -- 是 --> X
AB -- 否 --> AC[抛出 JSONDecodeError]
AA -- 是 --> AD[应用 object_hook/object_pairs_hook]
P & V & W & H & I & J & AD --> AE[返回解析结果与新索引]
AE --> C
C --> AF[所有内容解析完毕]
AF --> AG[返回最终 Python 对象]
CustomDecoder (继承自 json.JSONDecoder)
├── 字段: parse_object, parse_string, scan_once
├── 方法: __init__, decode
└── 依赖的全局函数
├── py_make_scanner
├── JSONObject
└── py_scanstring (别名 scanstring)用于匹配JSON数字(整数、浮点数、科学计数法)的正则表达式模式对象。
类型:re.Pattern
正则表达式编译标志的组合,包含re.VERBOSE、re.MULTILINE和re.DOTALL,用于定义正则表达式的匹配行为。
类型:int
用于匹配双引号字符串内容的正则表达式模式对象,用于扫描和解析JSON字符串。
类型:re.Pattern
用于匹配单引号字符串内容的正则表达式模式对象,用于扫描和解析JSON字符串。
类型:re.Pattern
用于匹配三双引号字符串内容的正则表达式模式对象,用于扫描和解析JSON字符串。
类型:re.Pattern
用于匹配三单引号字符串内容的正则表达式模式对象,用于扫描和解析JSON字符串。
类型:re.Pattern
转义字符映射字典,将JSON转义序列(如\n, \t)映射到对应的Python字符。
类型:dict
用于匹配JSON中空白字符(空格、制表符、换行符、回车符)的正则表达式模式对象。
类型:re.Pattern
包含所有JSON空白字符的字符串,用于快速检查字符是否为空白。
类型:str
指向JSONObject函数的引用,用于解析JSON对象(字典)。
类型:function
指向py_scanstring函数的引用,用于解析JSON字符串(包括单引号和三引号变体)。
类型:function
指向由py_make_scanner生成的扫描函数的引用,用于迭代解析JSON值。
类型:function
py_make_scanner 是一个工厂函数,用于创建 JSON 解码器的核心扫描函数 scan_once。它通过闭包捕获解码器上下文(如 parse_object, parse_string, strict 等配置),并返回一个能够根据输入字符串的当前位置识别并解析下一个 JSON 值(如对象、数组、字符串、数字、布尔值、null 等)的函数。该函数是 Python 标准库 json 模块中 C 语言实现的 make_scanner 的纯 Python 替代实现。
参数:
context:object,一个包含 JSON 解码器配置和方法的上下文对象。该对象应至少具有以下属性:parse_object,parse_array,parse_string,strict,parse_float,parse_int,parse_constant,object_hook,object_pairs_hook,memo。
返回值:function,返回一个名为 scan_once 的函数。该函数接受一个字符串和一个起始索引,返回一个元组 (parsed_value, new_index),其中 parsed_value 是解析出的 JSON 值,new_index 是解析后字符串中的新位置索引。
flowchart TD
A[py_make_scanner(context)] --> B[捕获上下文变量<br>parse_object, parse_string, strict等]
B --> C[定义内部函数 _scan_once]
C --> D{检查索引处字符 nextchar}
D -- 引号 ' 或 " --> E[调用 parse_string 解析字符串]
D -- { --> F[调用 parse_object 解析对象]
D -- [ --> G[调用 parse_array 解析数组]
D -- n 且后续为 "null" --> H[返回 None 和新索引]
D -- t 且后续为 "true" --> I[返回 True 和新索引]
D -- f 且后续为 "false" --> J[返回 False 和新索引]
D -- 数字或 - --> K[使用正则匹配数字]
K --> L{匹配成功?}
L -- 是 --> M[根据是否有小数/指数部分<br>调用 parse_float 或 parse_int]
L -- 否 --> N{检查特殊常量<br>NaN, Infinity, -Infinity}
N -- 匹配 --> O[调用 parse_constant]
N -- 不匹配 --> P[抛出 StopIteration 异常]
D -- 其他字符 --> P
E & F & G & H & I & J & M & O --> Q[返回 (解析结果, 新索引)]
P --> R[异常终止流程]
C --> S[定义内部函数 scan_once]
S --> T[调用 _scan_once]
T --> U{是否发生异常?}
U -- 是 --> V[抛出异常]
U -- 否 --> W[清空 memo 缓存]
W --> X[返回 _scan_once 的结果]
A --> Y[返回 scan_once 函数]
def py_make_scanner(context):
# 从上下文对象中捕获解码所需的所有函数和配置参数,形成闭包。
# 这些变量在返回的 `scan_once` 函数及其内部函数 `_scan_once` 中可用。
parse_object = context.parse_object # 解析JSON对象的函数
parse_array = context.parse_array # 解析JSON数组的函数
parse_string = context.parse_string # 解析JSON字符串的函数
match_number = NUMBER_RE.match # 匹配数字的正则表达式方法
strict = context.strict # 是否启用严格模式(如控制字符检查)
parse_float = context.parse_float # 解析浮点数的函数(默认float)
parse_int = context.parse_int # 解析整数的函数(默认int)
parse_constant = context.parse_constant # 解析常量(NaN, Infinity)的函数
object_hook = context.object_hook # 对象解码后的钩子函数
object_pairs_hook = context.object_pairs_hook # 对象解码为键值对列表后的钩子函数
memo = context.memo # 用于缓存字符串键的字典,优化性能
def _scan_once(string, idx):
"""内部扫描函数,负责识别并解析从idx开始的第一个完整JSON值。"""
try:
# 获取当前索引位置的字符
nextchar = string[idx]
except IndexError:
# 如果索引超出字符串范围,抛出StopIteration,其value属性为当前索引
raise StopIteration(idx) from None
# 根据当前字符判断JSON值的类型,并调用相应的解析函数
if nextchar in ("'", '"'):
# 处理字符串
# 检查是否为三引号字符串(如 `"""` 或 `'''`)
if idx + 2 < len(string) and string[idx + 1] == nextchar and string[idx + 2] == nextchar:
# 是三引号,跳过开头的三个引号字符进行解析
return parse_string(string, idx + 3, strict, delimiter=nextchar * 3)
else:
# 是单引号或双引号,跳过一个引号字符进行解析
return parse_string(string, idx + 1, strict, delimiter=nextchar)
elif nextchar == "{":
# 处理对象,调用parse_object,并传入必要的参数(包括递归调用自身的_scan_once)
return parse_object((string, idx + 1), strict, _scan_once, object_hook, object_pairs_hook, memo)
elif nextchar == "[":
# 处理数组,调用parse_array
return parse_array((string, idx + 1), _scan_once)
elif nextchar == "n" and string[idx : idx + 4] == "null":
# 处理null字面量
return None, idx + 4
elif nextchar == "t" and string[idx : idx + 4] == "true":
# 处理true字面量
return True, idx + 4
elif nextchar == "f" and string[idx : idx + 5] == "false":
# 处理false字面量
return False, idx + 5
# 尝试匹配数字
m = match_number(string, idx)
if m is not None:
integer, frac, exp = m.groups()
if frac or exp:
# 有小数部分或指数部分,按浮点数解析
res = parse_float(integer + (frac or "") + (exp or ""))
else:
# 没有小数和指数部分,按整数解析
res = parse_int(integer)
# 返回解析结果和正则匹配结束的位置
return res, m.end()
# 处理非标准JSON数字常量(来自JavaScript)
elif nextchar == "N" and string[idx : idx + 3] == "NaN":
return parse_constant("NaN"), idx + 3
elif nextchar == "I" and string[idx : idx + 8] == "Infinity":
return parse_constant("Infinity"), idx + 8
elif nextchar == "-" and string[idx : idx + 9] == "-Infinity":
return parse_constant("-Infinity"), idx + 9
else:
# 无法识别的字符,抛出StopIteration
raise StopIteration(idx)
def scan_once(string, idx):
"""对外暴露的扫描函数。在每次扫描后清空memo缓存。"""
try:
# 调用内部扫描函数进行实际解析
return _scan_once(string, idx)
finally:
# 无论解析成功与否,最后都清空memo缓存。
# memo用于在解析单个对象时缓存键字符串,避免重复创建。
# 清空它确保了解析不同对象或数组元素时的独立性。
memo.clear()
# 返回最终构造好的扫描函数
return scan_onceJSONObject 函数是 JSON 解码器的核心组件,负责解析 JSON 字符串中的对象(即键值对集合)。它从给定的字符串和起始索引开始,逐个字符扫描,识别键和值,处理分隔符和空白字符,并最终根据提供的钩子函数(如 object_hook 或 object_pairs_hook)构建并返回解析后的 Python 对象(通常是字典)以及解析结束后的新索引位置。
参数:
s_and_end:tuple,一个包含待解析字符串s和当前解析索引end的元组。strict:bool,如果为True,则强制执行严格的 JSON 字符串解码规则(例如,禁止未转义的控制字符);如果为False,则允许字面控制字符。scan_once:callable,一个用于扫描和解析 JSON 值(如字符串、数字、数组、对象等)的回调函数。object_hook:callable,如果指定,此函数将在对象被解析为字典后被调用,其返回值将替代默认的字典。object_pairs_hook:callable,如果指定,此函数将在对象被解析为键值对列表后被调用,其返回值将替代默认的字典。memo:dict,可选,一个用于缓存字符串键以优化性能的字典。默认为None。_w:function,一个用于匹配空白字符的正则表达式函数。默认为WHITESPACE.match。_ws:str,一个包含空白字符(空格、制表符、换行符、回车符)的字符串。默认为WHITESPACE_STR。
返回值:tuple,返回一个元组,包含解析后的对象(可能是字典或 object_pairs_hook 的返回值)和解析结束后在字符串中的新索引位置。
flowchart TD
A[开始: JSONObject(s_and_end, strict, ...)] --> B[解包 s_and_end 为 s, end<br>初始化 pairs 列表和 memo]
B --> C{检查下一个字符 nextchar}
C -->|nextchar 是空白| D[跳过空白,更新 end 和 nextchar]
D --> C
C -->|nextchar 是 '}'| E[处理空对象<br>应用 object_pairs_hook/object_hook]
E --> F[返回 空对象, end+1]
C -->|nextchar 不是 '"' 或 "'"| G[抛出 JSONDecodeError]
C -->|nextchar 是 '"' 或 "'"| H[end += 1<br>进入主循环]
H --> I[循环: 解析键值对]
I --> J[调用 scanstring 解析键 key]
J --> K[使用 memo 缓存键]
K --> L{检查键后字符是否为 ':'}
L -->|不是 ':'| M[跳过空白,再次检查]
M -->|仍不是 ':'| N[抛出 JSONDecodeError]
L -->|是 ':'| O[end += 1<br>跳过值前的空白]
M -->|是 ':'| O
O --> P[调用 scan_once 解析值 value]
P --> Q[将 (key, value) 加入 pairs 列表]
Q --> R[检查对象结束或下一个键]
R -->|nextchar 是 '}'| S[跳出循环]
R -->|nextchar 是 ','| T[end += 1<br>跳过空白,期待下一个键]
T --> U{检查下一个字符是否为引号}
U -->|是| I
U -->|不是| V[抛出 JSONDecodeError]
R -->|nextchar 是其他| V
S --> W[循环结束]
W --> X{检查 object_pairs_hook}
X -->|存在| Y[调用 object_pairs_hook(pairs)]
Y --> Z[返回 hook_result, end]
X -->|不存在| AA[将 pairs 列表转为字典]
AA --> BB{检查 object_hook}
BB -->|存在| CC[调用 object_hook(dict_pairs)]
CC --> Z
BB -->|不存在| DD[返回 dict_pairs, end]
def JSONObject(
s_and_end, strict, scan_once, object_hook, object_pairs_hook, memo=None, _w=WHITESPACE.match, _ws=WHITESPACE_STR
):
"""Parse a JSON object from a string and return the parsed object.
Args:
s_and_end (tuple): A tuple containing the input string to parse and the current index within the string.
strict (bool): If `True`, enforces strict JSON string decoding rules.
If `False`, allows literal control characters in the string. Defaults to `True`.
scan_once (callable): A function to scan and parse JSON values from the input string.
object_hook (callable): A function that, if specified, will be called with the parsed object as a dictionary.
object_pairs_hook (callable): A function that, if specified, will be called with the parsed object as a list of pairs.
memo (dict, optional): A dictionary used to memoize string keys for optimization. Defaults to None.
_w (function): A regular expression matching function for whitespace. Defaults to WHITESPACE.match.
_ws (str): A string containing whitespace characters. Defaults to WHITESPACE_STR.
Returns:
tuple or dict: A tuple containing the parsed object and the index of the character in the input string
after the end of the object.
"""
# 1. 解包输入参数,获取字符串和起始索引
s, end = s_and_end
# 2. 初始化用于存储键值对的列表
pairs = []
pairs_append = pairs.append # 优化:缓存 append 方法引用
# 3. 处理 memo 参数,如果为 None 则初始化为空字典
# Backwards compatibility
if memo is None:
memo = {}
memo_get = memo.setdefault # 优化:缓存 setdefault 方法引用
# 4. 获取当前待解析的字符(对象应以 '{' 开头,但此函数在 '{' 之后被调用,所以期待的是键或 '}')
# Use a slice to prevent IndexError from being raised, the following
# check will raise a more specific ValueError if the string is empty
nextchar = s[end : end + 1]
# Normally we expect nextchar == '"'
# 5. 检查第一个有效字符。如果不是引号,可能是空白或直接结束。
if nextchar != '"' and nextchar != "'":
# 5a. 如果是空白字符,则跳过
if nextchar in _ws:
end = _w(s, end).end()
nextchar = s[end : end + 1]
# 5b. 如果跳过空白后是 '}',说明是空对象
# Trivial empty object
if nextchar == "}":
# 根据是否有 object_pairs_hook 决定返回值
if object_pairs_hook is not None:
result = object_pairs_hook(pairs)
return result, end + 1
pairs = {}
if object_hook is not None:
pairs = object_hook(pairs)
return pairs, end + 1
# 5c. 如果不是引号也不是结束符,则报错
elif nextchar != '"':
raise JSONDecodeError("Expecting property name enclosed in double quotes", s, end)
# 6. 第一个字符是引号,将索引移动到引号之后,开始解析键
end += 1
# 7. 主循环:解析键值对
while True:
# 7a. 解析键(字符串)。支持单引号、双引号及其三引号变体。
if end + 1 < len(s) and s[end] == nextchar and s[end + 1] == nextchar:
# Handle the case where the next two characters are the same as nextchar
key, end = scanstring(s, end + 2, strict, delimiter=nextchar * 3) # triple quote
else:
# Handle the case where the next two characters are not the same as nextchar
key, end = scanstring(s, end, strict, delimiter=nextchar)
# 7b. 使用 memo 字典缓存键字符串,避免相同字符串重复创建。
key = memo_get(key, key)
# 7c. 查找键后的 ':' 分隔符。优化常见情况(": " 或 ":")。
# To skip some function call overhead we optimize the fast paths where
# the JSON key separator is ": " or just ":".
if s[end : end + 1] != ":":
end = _w(s, end).end()
if s[end : end + 1] != ":":
raise JSONDecodeError("Expecting ':' delimiter", s, end)
end += 1 # 跳过 ':'
# 7d. 跳过值前的空白字符(可选)。
try:
if s[end] in _ws:
end += 1
if s[end] in _ws:
end = _w(s, end + 1).end()
except IndexError:
pass # 如果已到字符串末尾,IndexError 是正常的
# 7e. 调用 scan_once 解析值(可以是任何 JSON 类型)。
try:
value, end = scan_once(s, end)
except StopIteration as err:
raise JSONDecodeError("Expecting value", s, err.value) from None
# 7f. 将解析出的键值对添加到列表中。
pairs_append((key, value))
# 7g. 查看当前字符,判断对象是否结束或是否有下一个键值对。
try:
nextchar = s[end]
if nextchar in _ws:
end = _w(s, end + 1).end()
nextchar = s[end]
except IndexError:
nextchar = "" # 字符串结束
end += 1 # 移动到下一个待检查的字符
# 7h. 如果遇到 '}',对象解析结束。
if nextchar == "}":
break
# 7i. 如果遇到 ',',说明还有更多键值对,继续循环。
elif nextchar != ",":
raise JSONDecodeError("Expecting ',' delimiter", s, end - 1)
# 7j. 处理 ',' 之后的部分:跳过空白,期待下一个键(必须以引号开始)。
end = _w(s, end).end()
nextchar = s[end : end + 1]
end += 1
if nextchar != '"':
raise JSONDecodeError("Expecting property name enclosed in double quotes", s, end - 1)
# 8. 循环结束,所有键值对已解析完毕。
# 9. 根据提供的钩子函数决定最终返回值。
if object_pairs_hook is not None:
result = object_pairs_hook(pairs)
return result, end
# 10. 如果没有 object_pairs_hook,则将列表转换为字典。
pairs = dict(pairs)
if object_hook is not None:
pairs = object_hook(pairs)
# 11. 返回解析后的对象和新的索引位置。
return pairs, endpy_scanstring 函数是 JSON 解码器的核心组件,负责从输入字符串中扫描并解码 JSON 字符串字面量。它处理转义序列(包括 Unicode 转义)、控制字符验证,并支持单引号、双引号以及它们的三引号变体作为字符串分隔符。
参数:
s:str,包含待解码 JSON 字符串的输入字符串。end:int,输入字符串s中起始引号之后字符的索引。strict:bool,如果为True,则强制执行严格的 JSON 字符串解码规则(例如,禁止字面控制字符)。如果为False,则允许字面控制字符。默认为True。_b:dict,包含标准 JSON 转义序列(如\n,\t)映射的字典。默认为预定义的BACKSLASH字典。_m:function,用于匹配字符串块(内容与终止符)的正则表达式匹配函数。默认为STRINGCHUNK.match。delimiter:str,用于定义 JSON 字符串开始和结束的字符串分隔符。可以是"、'、\"\"\"或'''。默认为"。
返回值:tuple,一个包含解码后的字符串和输入字符串 s 中结束引号之后字符索引的元组。
flowchart TD
A[开始: py_scanstring(s, end, ...)] --> B[初始化: chunks = [], begin = end - 1]
B --> C{根据 delimiter 选择匹配函数 _m}
C --> D[循环: 使用 _m 匹配字符串块]
D --> E{匹配成功?}
E -- 否 --> F[抛出 JSONDecodeError<br>“Unterminated string”]
E -- 是 --> G[更新 end = chunk.end()<br>获取 content, terminator]
G --> H{content 非空?}
H -- 是 --> I[chunks.append(content)]
H -- 否 --> J{terminator == delimiter?}
J -- 是 --> K[循环结束]
J -- 否 --> L{terminator == '\\'?}
L -- 否 --> M{strict == True?}
M -- 是 --> N[抛出 JSONDecodeError<br>“Invalid control character”]
M -- 否 --> O[chunks.append(terminator)]
O --> D
L -- 是 --> P[获取转义字符 esc = s[end]]
P --> Q{esc == 'u'?<br>Unicode转义?}
Q -- 否 --> R[从 _b 查找 esc<br>映射为 char]
R --> S{映射成功?}
S -- 否 --> T[抛出 JSONDecodeError<br>“Invalid \\escape”]
S -- 是 --> U[end += 1]
U --> V[chunks.append(char)]
V --> D
Q -- 是 --> W[调用 _decode_uXXXX(s, end)<br>解码4位十六进制为 uni]
W --> X[end += 5]
X --> Y{uni 是 UTF-16 代理对的高半部分?<br>且后跟 '\\u'?}
Y -- 是 --> Z[解码低半部分 uni2]
Z --> AA[组合为完整 Unicode 码点<br>char = chr(组合后码点)]
AA --> AB[end += 6]
AB --> V
Y -- 否 --> AC[char = chr(uni)]
AC --> V
K --> AD[返回 ''.join(chunks), end]
def py_scanstring(s, end, strict=True, _b=BACKSLASH, _m=STRINGCHUNK.match, delimiter='"'):
"""Scan the string s for a JSON string.
Args:
s (str): The input string to be scanned for a JSON string.
end (int): The index of the character in `s` after the quote that started the JSON string.
strict (bool): If `True`, enforces strict JSON string decoding rules.
If `False`, allows literal control characters in the string. Defaults to `True`.
_b (dict): A dictionary containing escape sequence mappings.
_m (function): A regular expression matching function for string chunks.
delimiter (str): The string delimiter used to define the start and end of the JSON string.
Can be one of: '"', "'", '\"""', or "'''". Defaults to '"'.
Returns:
tuple: A tuple containing the decoded string and the index of the character in `s`
after the end quote.
"""
# 存储解码后字符串块的列表
chunks = []
_append = chunks.append
# 记录字符串起始引号的位置,用于错误信息
begin = end - 1
# 根据分隔符选择对应的正则匹配函数
if delimiter == '"':
_m = STRINGCHUNK.match
elif delimiter == "'":
_m = STRINGCHUNK_SINGLEQUOTE.match
elif delimiter == '"""':
_m = STRINGCHUNK_TRIPLE_DOUBLE_QUOTE.match
else:
_m = STRINGCHUNK_TRIPLE_SINGLEQUOTE.match
# 主循环:逐块匹配和处理字符串内容
while 1:
# 使用选定的正则表达式匹配下一个块
chunk = _m(s, end)
if chunk is None:
# 未找到匹配项,意味着字符串未正确终止
raise JSONDecodeError("Unterminated string starting at", s, begin)
# 更新索引到当前匹配块的结束位置
end = chunk.end()
# 分组:content 是未转义的文本,terminator 是终止符(引号、控制字符或反斜杠)
content, terminator = chunk.groups()
# 如果 content 非空,将其添加到块列表中
if content:
_append(content)
# 检查终止符类型
if terminator == delimiter:
# 遇到结束引号,字符串扫描完成
break
elif terminator != "\\":
# 终止符既不是结束引号也不是转义符,它是一个字面控制字符
if strict:
# 严格模式下,抛出错误
msg = "Invalid control character {0!r} at".format(terminator)
raise JSONDecodeError(msg, s, end)
else:
# 非严格模式下,将其作为普通字符处理
_append(terminator)
continue
# 处理转义序列:terminator 是 '\\'
try:
# 获取转义字符
esc = s[end]
except IndexError:
# 字符串在转义序列中途结束
raise JSONDecodeError("Unterminated string starting at", s, begin) from None
# 处理非Unicode转义(如 \n, \t, \" 等)
if esc != "u":
try:
# 从转义映射表中查找对应的字符
char = _b[esc]
except KeyError:
# 无效的转义序列
msg = "Invalid \\escape: {0!r}".format(esc)
raise JSONDecodeError(msg, s, end)
# 成功解码,索引前进1位(跳过转义字符)
end += 1
else:
# 处理Unicode转义序列:\uXXXX
# 调用辅助函数解码4位十六进制数为整数
uni = _decode_uXXXX(s, end)
# 索引前进5位(跳过 \u 和4位十六进制数)
end += 5
# 检查解码出的整数是否是UTF-16代理对的高半部分(High Surrogate)
# 并且后面紧跟另一个Unicode转义序列
if 0xD800 <= uni <= 0xDBFF and s[end : end + 2] == "\\u":
# 解码低半部分(Low Surrogate)
uni2 = _decode_uXXXX(s, end + 1)
# 检查是否是有效的低半部分
if 0xDC00 <= uni2 <= 0xDFFF:
# 组合高半部分和低半部分,计算完整的Unicode码点
uni = 0x10000 + (((uni - 0xD800) << 10) | (uni2 - 0xDC00))
# 索引再前进6位(跳过第二个 \u 和4位十六进制数)
end += 6
# 将Unicode码点转换为字符
char = chr(uni)
# 将解码出的字符添加到块列表中
_append(char)
# 循环结束,将所有块连接成完整的字符串,并返回字符串和结束索引
return "".join(chunks), end该函数是 JSON 解码器的核心组件,负责从输入字符串中扫描并解码一个 JSON 字符串字面量。它处理转义序列(如 \n, \uXXXX),支持单引号、双引号以及它们的三引号变体作为分隔符,并根据 strict 参数决定是否允许字面控制字符。
参数:
s:str,包含待解码 JSON 字符串的完整输入字符串。end:int,输入字符串s中的索引,指向起始引号之后的一个字符。strict:bool,如果为True,则严格执行 JSON 规范,禁止未转义的控制字符;如果为False,则允许它们。默认为True。_b:dict,包含基本转义序列(如\n,\t)映射的字典。默认为预定义的BACKSLASH字典。_m:function,用于匹配字符串块(内容+终止符)的正则表达式匹配函数。默认为STRINGCHUNK.match。delimiter:str,用于界定字符串开始和结束的引号字符。可以是"、'、"""或'''。默认为"。
返回值:tuple,一个包含两个元素的元组:第一个元素是解码后的 Python 字符串,第二个元素是输入字符串 s 中结束引号之后字符的索引。
flowchart TD
A[开始: py_scanstring(s, end, ...)] --> B[初始化: chunks列表, 根据delimiter选择_m]
B --> C{循环: 使用_m匹配字符串块}
C -- 匹配成功 --> D[提取内容content和终止符terminator]
D --> E{terminator类型判断}
E -- "是结束引号(delimiter)" --> F[循环结束]
E -- "是反斜杠(\)" --> G[处理转义序列]
E -- "是控制字符(严格模式)" --> H[抛出JSONDecodeError]
E -- "是控制字符(非严格模式)" --> I[将字符加入chunks]
G --> J{转义字符类型判断}
J -- "是'u'(Unicode转义)" --> K[调用_decode_uXXXX解码]
K --> L[处理可能的UTF-16代理对]
L --> M[将解码字符加入chunks]
J -- "是其他字符" --> N[从_b字典查找映射]
N --> O[将映射字符加入chunks]
I --> C
M --> C
O --> C
C -- 匹配失败(None) --> P[抛出JSONDecodeError<br>字符串未终止]
F --> Q[将chunks连接成字符串并返回<br>及当前索引end]
def py_scanstring(s, end, strict=True, _b=BACKSLASH, _m=STRINGCHUNK.match, delimiter='"'):
"""Scan the string s for a JSON string.
Args:
s (str): The input string to be scanned for a JSON string.
end (int): The index of the character in `s` after the quote that started the JSON string.
strict (bool): If `True`, enforces strict JSON string decoding rules.
If `False`, allows literal control characters in the string. Defaults to `True`.
_b (dict): A dictionary containing escape sequence mappings.
_m (function): A regular expression matching function for string chunks.
delimiter (str): The string delimiter used to define the start and end of the JSON string.
Can be one of: '"', "'", '\"""', or "'''". Defaults to '"'.
Returns:
tuple: A tuple containing the decoded string and the index of the character in `s`
after the end quote.
"""
# 用于收集字符串片段的列表
chunks = []
# 优化:将append方法本地化,避免每次循环中的属性查找
_append = chunks.append
# 记录字符串开始的引号位置,用于错误信息
begin = end - 1
# 根据指定的分隔符选择对应的正则表达式匹配函数
if delimiter == '"':
_m = STRINGCHUNK.match
elif delimiter == "'":
_m = STRINGCHUNK_SINGLEQUOTE.match
elif delimiter == '"""':
_m = STRINGCHUNK_TRIPLE_DOUBLE_QUOTE.match
else:
_m = STRINGCHUNK_TRIPLE_SINGLEQUOTE.match
# 主循环:持续匹配字符串内容,直到遇到结束引号
while 1:
# 使用选定的正则表达式匹配函数查找下一个“块”
# 一个块包含:普通内容 + 终止符(结束引号、控制字符或反斜杠)
chunk = _m(s, end)
# 如果匹配失败,说明字符串未正确终止
if chunk is None:
raise JSONDecodeError("Unterminated string starting at", s, begin)
# 更新索引到当前匹配块的末尾
end = chunk.end()
# 将匹配结果分为两部分:普通字符串内容 和 终止符
content, terminator = chunk.groups()
# 如果存在未转义的普通内容,直接加入结果列表
if content:
_append(content)
# 根据终止符的类型决定下一步操作
# 情况1:终止符就是结束引号,字符串扫描完成
if terminator == delimiter:
break
# 情况2:终止符是字面控制字符(非反斜杠)
elif terminator != "\\":
# 严格模式下,这是错误
if strict:
msg = "Invalid control character {0!r} at".format(terminator)
raise JSONDecodeError(msg, s, end)
# 非严格模式下,允许该字符,将其加入结果并继续循环
else:
_append(terminator)
continue
# 情况3:终止符是反斜杠,表示后面跟着一个转义序列
try:
# 获取转义字符
esc = s[end]
except IndexError:
# 如果字符串在反斜杠后结束,也是未终止错误
raise JSONDecodeError("Unterminated string starting at", s, begin) from None
# 处理转义序列
# 子情况3a:不是Unicode转义(\uXXXX)
if esc != "u":
try:
# 从预定义的转义映射字典中查找对应的字符
char = _b[esc]
except KeyError:
# 如果转义字符无效,抛出错误
msg = "Invalid \\escape: {0!r}".format(esc)
raise JSONDecodeError(msg, s, end)
# 成功解码一个简单转义序列,索引前进1位
end += 1
# 子情况3b:是Unicode转义序列(\uXXXX)
else:
# 调用辅助函数解码4位十六进制数为Unicode码点
uni = _decode_uXXXX(s, end)
# 索引前进5位(\u + 4位十六进制数)
end += 5
# 处理UTF-16代理对:如果当前解码的码点在代理对高位范围内,
# 并且后面紧跟着另一个Unicode转义序列
if 0xD800 <= uni <= 0xDBFF and s[end : end + 2] == "\\u":
# 解码第二个码点(低位代理)
uni2 = _decode_uXXXX(s, end + 1)
# 检查第二个码点是否在低位代理范围内
if 0xDC00 <= uni2 <= 0xDFFF:
# 组合高位和低位代理,计算实际的Unicode码点
uni = 0x10000 + (((uni - 0xD800) << 10) | (uni2 - 0xDC00))
# 索引再前进6位(第二个\uXXXX)
end += 6
# 将最终的Unicode码点转换为Python字符
char = chr(uni)
# 将解码得到的字符(无论是简单转义还是Unicode转义)加入结果列表
_append(char)
# 循环结束,将所有收集的字符串片段连接起来,并返回结果及当前索引
return "".join(chunks), end该方法用于初始化一个自定义的 JSON 解码器 (CustomDecoder) 实例。它继承自 json.JSONDecoder,并重写了部分解析组件,以支持自定义的对象钩子、数值解析、常量解析以及更灵活的字符串解析(例如支持单引号和双引号字符串)。该方法通过调用父类的 __init__ 方法完成基础初始化,然后替换默认的 parse_object、parse_string 和 scan_once 方法为自定义实现。
参数:
object_hook:Optional[Callable[[Dict[Any, Any]], Any]],可选参数,用于在解析 JSON 对象时调用的钩子函数,接收解析后的字典作为参数,返回自定义对象。parse_float:Optional[Callable[[str], Any]],可选参数,用于解析 JSON 浮点数的自定义函数。parse_int:Optional[Callable[[str], Any]],可选参数,用于解析 JSON 整数的自定义函数。parse_constant:Optional[Callable[[str], Any]],可选参数,用于解析 JSON 常量(如NaN、Infinity)的自定义函数。strict:bool,可选参数,默认为True,表示是否启用严格的 JSON 字符串解析(禁止控制字符)。object_pairs_hook:Optional[Callable[[List[Tuple[str, Any]]], Any]],可选参数,用于在解析 JSON 对象时调用的钩子函数,接收键值对列表作为参数,返回自定义对象。
返回值:None,该方法不返回任何值,仅用于初始化 CustomDecoder 实例。
flowchart TD
A[开始初始化 CustomDecoder] --> B[调用父类 JSONDecoder.__init__<br>传递所有参数]
B --> C[设置 self.parse_object = JSONObject]
C --> D[设置 self.parse_string = py_scanstring]
D --> E[调用 py_make_scanner(self) 生成 scan_once 函数]
E --> F[设置 self.scan_once = 生成的函数]
F --> G[初始化完成]
def __init__(
self,
*,
object_hook=None,
parse_float=None,
parse_int=None,
parse_constant=None,
strict=True,
object_pairs_hook=None
):
# 调用父类 json.JSONDecoder 的初始化方法,传递所有参数以保持基础功能
super().__init__(
object_hook=object_hook,
parse_float=parse_float,
parse_int=parse_int,
parse_constant=parse_constant,
strict=strict,
object_pairs_hook=object_pairs_hook,
)
# 将默认的对象解析器替换为自定义的 JSONObject 函数
self.parse_object = JSONObject
# 将默认的字符串解析器替换为自定义的 py_scanstring 函数
self.parse_string = py_scanstring
# 使用 py_make_scanner 函数生成一个扫描器,并将其赋值给实例的 scan_once 属性
# 该扫描器将使用当前实例(self)作为上下文,从而能够访问上面设置的自定义解析器
self.scan_once = py_make_scanner(self)该方法重写了标准库 json.JSONDecoder 的 decode 方法,用于解码一个 JSON 格式的字符串。它继承了父类的所有配置(如 object_hook, parse_float 等),并使用了自定义的扫描器 (scan_once) 和对象解析器 (parse_object),以支持扩展的 JSON 语法(如单引号字符串和三引号字符串)。其核心功能是解析输入字符串,跳过前导空白字符,然后调用内部扫描器递归地构建 Python 对象。
参数:
s:str,需要解码的 JSON 格式字符串。_w:function,一个用于匹配空白字符的正则表达式函数,默认为json.decoder.WHITESPACE.match。
返回值:Any,解码后对应的 Python 对象(如 dict, list, str, int, float, bool, None)。
flowchart TD
A[开始解码] --> B[调用父类 decode 方法]
B --> C[父类方法处理<br>(跳过空白,调用 scan_once 等)]
C --> D[返回解码后的 Python 对象]
D --> E[结束]
def decode(self, s, _w=json.decoder.WHITESPACE.match):
# 直接调用父类 json.JSONDecoder 的 decode 方法。
# 父类的 decode 方法会处理前导空白字符,并调用 self.scan_once 来递归解析 JSON 字符串。
# 由于在 __init__ 中已将 self.scan_once 和 self.parse_object 等替换为自定义版本,
# 因此此调用实际上使用了自定义的解析逻辑。
return super().decode(s)一个扩展了Python标准库json.JSONDecoder的类,用于提供自定义的JSON解析行为,特别是支持单引号和三引号字符串。
一个工厂函数,用于创建scan_once函数。该函数是JSON解码器的核心扫描器,负责识别并解析JSON字符串中的下一个有效令牌(如对象、数组、字符串、数字、布尔值、null等),并支持单引号和三引号字符串的识别。
一个用于解析JSON对象的函数。它处理对象键值对的扫描、分隔符检查,并支持通过object_hook和object_pairs_hook进行自定义对象构建。它包含了处理单引号和三引号字符串键的逻辑。
一个用于扫描和解析JSON字符串字面量的函数。它处理转义序列(包括Unicode转义),并支持四种字符串分隔符:双引号(")、单引号(')、三双引号(""")和三单引号(''')。它根据传入的分隔符选择不同的正则表达式进行高效匹配。
一系列预编译的正则表达式,用于高效匹配JSON数字和字符串内容。它们是解码器性能的关键,负责识别数字字面量以及将字符串分割为未转义内容块和终止符(引号、反斜杠或控制字符)。
一个字典,将JSON转义字符(如\n, \t)映射到其对应的Python字符。用于快速解析简单的转义序列。
通过context参数(在py_make_scanner中)将解码器的配置(如strict模式、parse_float、object_hook等)和关键函数(parse_object, parse_string)传递给扫描器,实现了配置的集中管理和功能的可插拔性。
- 单引号字符串支持不完整:代码通过修改
_scan_once和py_scanstring函数,尝试支持使用单引号 (') 和三引号 (''',""") 作为字符串分隔符。然而,这种修改可能破坏了与标准json模块的严格兼容性,因为标准 JSON 规范仅允许双引号。这可能导致解析非标准 JSON 时行为不一致,或在与期望严格 JSON 的外部系统交互时出现问题。 memo缓存清理时机不当:在scan_once函数中,memo.clear()在finally块中被调用,这意味着每次扫描一个值(无论成功与否)后,用于优化字符串键的缓存都会被清空。这削弱了memo的设计初衷(在解析同一对象内重复键时优化性能),可能使其几乎无效,并带来不必要的清理开销。- 错误处理与异常信息:
_scan_once函数在遇到无法识别的字符时抛出StopIteration异常。虽然JSONObject函数会捕获并将其转换为JSONDecodeError,但使用StopIteration作为控制流和错误信号不符合其标准语义(表示迭代结束),可能使代码逻辑不易理解,并与 Python 3.7+ 中StopIteration在生成器中的行为变更产生潜在混淆。 - 硬编码的正则表达式和映射:
BACKSLASH转义映射和STRINGCHUNK_*系列正则表达式是硬编码的全局变量。虽然当前功能正常,但缺乏灵活性。如果需要支持自定义转义序列或不同的字符串分隔符模式,必须直接修改这些全局变量,这增加了维护成本和出错风险。 JSONObject函数参数过多:JSONObject函数接收大量参数(s_and_end,strict,scan_once,object_hook,object_pairs_hook,memo,_w,_ws),这使得函数签名冗长,调用不便,且降低了代码的可读性和可维护性。部分参数(如_w,_ws)作为默认参数注入以优化性能,但也使函数逻辑更复杂。
- 明确解析模式:建议在
CustomDecoder中引入一个明确的配置选项(例如allow_single_quotes=False),以控制是否启用非标准的单引号字符串支持。这可以提高代码的清晰度,并允许用户在需要严格兼容性与扩展功能之间做出选择。 - 优化
memo缓存策略:将memo.clear()的调用从scan_once的finally块中移除。更合理的策略是在JSONObject函数开始时初始化memo(如果为None),并让其在整个对象解析期间持续存在,在对象解析完成后自然释放。这样可以真正实现键的重复利用优化。 - 使用更合适的异常类型:考虑在
_scan_once函数中遇到解析错误时,直接抛出JSONDecodeError或一个自定义的、语义更清晰的异常(如ValueError子类),而不是StopIteration。这可以使错误传播路径更直接,代码意图更明确。 - 将配置抽象为类或结构:考虑将
BACKSLASH、STRINGCHUNK_*正则表达式、delimiter支持逻辑等配置信息封装到一个可配置的类(例如StringScannerConfig)或字典中。这样,CustomDecoder可以通过传入不同的配置实例来轻松改变字符串解析行为,提高代码的模块化和可测试性。 - 重构
JSONObject函数:- 减少参数:考虑将
scan_once,object_hook,object_pairs_hook,memo等紧密相关的参数封装到一个上下文对象(如ParseContext)中,作为单个参数传递。 - 提取辅助函数:将解析键值对、跳空白字符等逻辑提取为独立的内部函数,以降低主函数的圈复杂度,提高可读性。
- 性能权衡:评估将
_w和_ws作为参数注入带来的微优化是否值得增加的复杂度。在大多数场景下,直接使用模块级常量可能更简洁。
- 减少参数:考虑将
- 增强类型注解:为所有函数和方法添加完整的 Python 类型注解(Type Hints)。这可以显著提高代码在 IDE 中的可读性,方便静态类型检查工具(如 mypy)进行错误检测,并帮助其他开发者理解接口契约。
- 补充单元测试:针对单引号、三引号支持、
memo缓存行为、错误处理等新增或修改的功能,编写全面的单元测试。确保在各种边界情况(如转义字符、Unicode、嵌套结构)下解析行为符合预期,并防止未来回归。 - 文档更新:在
CustomDecoder的文档字符串中明确说明其扩展功能(如单引号支持)与标准json.JSONDecoder的差异。同时,为新增的复杂函数(如JSONObject,py_scanstring)提供更详细的参数说明和示例。
本代码的核心设计目标是实现一个功能增强的 JSON 解码器,在兼容 Python 标准库 json.JSONDecoder 的基础上,扩展对单引号字符串和三引号字符串(''' 和 """)的支持。主要约束包括:
- 向后兼容性:必须保持与标准
json.loads和json.JSONDecoder相同的 API 接口和行为,确保现有代码无需修改即可使用。 - 性能考量:作为解析器核心组件,需在保证功能正确性的前提下,尽量减少性能开销。代码中使用了正则表达式预编译、函数局部变量缓存(如
pairs_append = pairs.append)等优化手段。 - 可维护性:代码结构模仿了
json模块的内部实现,通过覆写JSONDecoder的parse_object、parse_string和scan_once方法来实现定制,保持了与标准库相似的逻辑流,便于理解和维护。 - 严格模式:支持标准 JSON 的严格模式(
strict=True),在此模式下禁止字符串中出现未转义的控制字符。
代码的错误处理机制紧密遵循 Python 标准库 json 模块的设计,主要抛出 json.JSONDecodeError 异常。
- 异常类型:统一使用
json.JSONDecodeError来指示解析过程中遇到的语法错误或格式错误。该异常提供了错误信息、原始字符串和错误位置,便于调试。 - 错误触发点:
_scan_once函数:在遇到无法识别的字符时,抛出StopIteration异常(被包装后重新抛出为JSONDecodeError("Expecting value", ...))。JSONObject函数:在期望属性名引号、冒号分隔符、逗号分隔符或右花括号时未找到,抛出JSONDecodeError。py_scanstring函数:在遇到未终止的字符串、无效的控制字符(严格模式下)或无效的转义序列时,抛出JSONDecodeError。
- 内部异常转换:
_scan_once中抛出的StopIteration异常在JSONObject中被捕获并转换为更具描述性的JSONDecodeError("Expecting value", ...)。 - 索引错误处理:在多个地方使用
try...except IndexError来安全地访问字符串索引,防止因字符串意外结束而导致的程序崩溃,并将其转化为相应的JSONDecodeError。
解析过程本质上是一个基于递归下降的、隐式状态机,其状态由当前解析索引和上下文决定。
- 启动与驱动:入口点为
CustomDecoder.decode(),它调用父类的decode方法。父类方法会调用self.scan_once(即py_make_scanner返回的scan_once函数)来驱动解析。 - 核心扫描器 (
scan_once/_scan_once):这是一个状态识别器。它根据当前索引 (idx) 指向的字符,判断下一个 JSON 值的类型(对象{、数组[、字符串"/'、字面量null/true/false、数字、NaN/Infinity),并委托给相应的解析函数或直接返回值。返回值是(parsed_value, new_index)元组,实现了状态的推进。 - 委托解析:
- 对象解析 (
JSONObject):进入此函数后,状态变为“解析对象”。它循环处理"key": value对,使用scan_once解析value,直到遇到}。scan_once的递归调用可以处理嵌套结构。 - 字符串解析 (
py_scanstring):根据给定的delimiter(单引号、双引号、三引号)进入字符串解析状态。使用不同的正则表达式 (STRINGCHUNK_*) 分块读取内容并处理转义序列,直到遇到结束引号。 - 数组解析:代码中未显式定义
parse_array,它使用了从父类上下文 (context.parse_array) 继承来的标准实现,逻辑类似对象解析,但处理的是值序列。
- 对象解析 (
- 数据传递:解析过程中的配置(如
strict,object_hook,parse_float等)通过context对象(即CustomDecoder实例)传递给py_make_scanner生成的闭包函数,进而传递给JSONObject和py_scanstring。 - 结果组装:解析出的键值对以列表形式收集在
JSONObject中,最后根据object_pairs_hook或object_hook的设置转换为最终对象。基础值(字符串、数字、字面量)由scan_once直接返回。
- 标准库依赖:
json:核心依赖,用于基类JSONDecoder、异常JSONDecodeError、空白匹配器WHITESPACE.match以及 Unicode 解码辅助函数_decode_uXXXX。re:用于编译正则表达式,进行数字、字符串分块和空白字符的模式匹配。
- 接口契约:
CustomDecoder类:其构造函数与json.JSONDecoder完全一致,接受object_hook,parse_float,parse_int,parse_constant,strict,object_pairs_hook参数。它保证了作为json.JSONDecoder子类的可替换性。py_make_scanner函数:预期接收一个context对象,该对象需具有parse_object,parse_array,parse_string,strict,parse_float,parse_int,parse_constant,object_hook,object_pairs_hook,memo属性。此契约与json模块内部用于创建扫描器的约定一致。JSONObject函数:其参数签名与json.decoder.JSONObject兼容,这是为了能够直接赋值给decoder.parse_object。它依赖于传入的scan_once函数来解析值。py_scanstring函数:设计为json.decoder.py_scanstring的增强版,增加了delimiter参数以支持多种引号。它内部依赖_decode_uXXXX函数处理 Unicode 转义,并期望_b(BACKSLASH)、_m(正则匹配函数) 等参数被注入以优化性能。
- 内部函数替换:
CustomDecoder通过self.parse_object = JSONObject和self.parse_string = py_scanstring替换了默认的解析函数,这是与json模块内部机制交互的关键。