chronaxis.configuration.json_parser
源码基线:8904c2f744951857659abd40aaa3535d57faf53c
1"""标准库负责JSON语法和数值;轻量第二遍只定位字段,不再构造第二份配置树。"""2from bisect import bisect_right3import json4from math import isfinite5import re67from .models import ConfigurationError, ConfigurationLocation89MAX_DEPTH=6410_SPACE=re.compile(r'[ \t\r\n]*')111213class _DuplicateKey(ValueError):14 """只用于让标准库停止覆盖重复键;随后位置扫描给出具体字段。"""151617def _unique_pairs(pairs):18 """建立唯一键对象。参数: 标准库键值对。返回: dict。异常: 重复键。"""19 result={}20 for key,value in pairs:21 if key in result:22 raise _DuplicateKey(key)23 result[key]=value24 return result252627class _Locations:28 """只记录JSON路径和偏移,不保留第二份值树;语法真值仍由json.loads决定。"""2930 def __init__(self, text, source):31 """建立本次扫描状态。参数: 文本/显示来源。返回: owner。异常: 分配失败。"""32 self.text,self.source=text,source33 self.positions={}34 self.lines=[match.start() for match in re.finditer('\n',text)]35 self.decoder=json.JSONDecoder()3637 def location(self, offset):38 """把字符偏移转换为1基行列。参数: 偏移。返回: 位置。异常: 无。"""39 line=bisect_right(self.lines,offset-1)40 previous=self.lines[line-1] if line else -141 return ConfigurationLocation(line+1,offset-previous)4243 def scan(self, offset=0, path=(), depth=0):44 """遍历合法JSON或已知含重复键的JSON。返回: 结束偏移。异常: 重复键/过深/语法错误。"""45 offset=_SPACE.match(self.text,offset).end()46 location=self.location(offset)47 if depth>MAX_DEPTH:48 raise ConfigurationError(self.source,path,location,'nesting_limit','配置嵌套不能超过64层')49 self.positions[path]=location50 first=self.text[offset]51 if first=='{':52 offset=_SPACE.match(self.text,offset+1).end()53 if self.text[offset]=='}':return offset+154 seen=set()55 while True:56 key_at=offset57 key,offset=json.decoder.scanstring(self.text,offset+1,True)58 if key in seen:59 raise ConfigurationError(self.source,(*path,key),self.location(key_at),60 'duplicate_key',f'重复键:{key}')61 seen.add(key)62 offset=_SPACE.match(self.text,offset).end()+1 # 已通过JSON语法检查的冒号。63 offset=self.scan(offset,(*path,key),depth+1)64 offset=_SPACE.match(self.text,offset).end()65 if self.text[offset]=='}':return offset+166 offset=_SPACE.match(self.text,offset+1).end()67 if first=='[':68 offset=_SPACE.match(self.text,offset+1).end()69 if self.text[offset]==']':return offset+170 index=071 while True:72 offset=self.scan(offset,(*path,index),depth+1)73 offset=_SPACE.match(self.text,offset).end()74 if self.text[offset]==']':return offset+175 offset=_SPACE.match(self.text,offset+1).end()76 index+=177 # raw_decode这里只接原子值,绝不重新解码整个容器。78 _,end=self.decoder.raw_decode(self.text,offset)79 return end808182def _finite_values(value, source, locations, path=()):83 """拒绝JSON宽松接受的NaN/Infinity及指数溢出。返回: None。异常: 定位到实际值。"""84 if type(value) is float and not isfinite(value):85 raise ConfigurationError(source,path,locations[path],'nonfinite_number','数值必须有限,不能使用NaN或Infinity')86 if type(value) is dict:87 for key,item in value.items():_finite_values(item,source,locations,(*path,key))88 elif type(value) is list:89 for index,item in enumerate(value):_finite_values(item,source,locations,(*path,index))909192def parse_json(text, source):93 """按标准JSON加载并附准确位置。参数: 文本/来源。返回: 数据和位置表。异常: ConfigurationError。"""94 try:95 try:96 data=json.loads(text,object_pairs_hook=_unique_pairs)97 except _DuplicateKey:98 # 标准库已找到实际重复;不从字符串搜索猜测它属于哪个嵌套对象。99 _Locations(text,source).scan()100 raise AssertionError('JSON重复键位置扫描没有发现重复')101 except json.JSONDecodeError:102 raise103 except (ValueError,OverflowError) as error:104 # Python的十进制整数安全长度限制等数值错误不能泄露为无文件定位的底层异常。105 raise ConfigurationError(source,(),None,'invalid_number','数字超出当前Python可解析的范围') from error106 scanner=_Locations(text,source)107 scanner.scan()108 _finite_values(data,source,scanner.positions)109 return data,scanner.positions110 except json.JSONDecodeError as error:111 raise ConfigurationError(source,(),ConfigurationLocation(error.lineno,error.colno),112 'invalid_json',error.msg) from error113 except RecursionError as error:114 raise ConfigurationError(source,(),None,'nesting_limit','配置嵌套不能超过64层') from error115