Python内置json模块加载JSON时无法解析$ref指针的解决方法
问题说明
我创建了一份JSON配置文件,在顶层ColoringLight节点集中定义了语法高亮的全套配色值,syntax子节点下的各语法项通过{"$ref": "#/路径"}格式的JSON指针引用ColoringLight节点下的对应色值。使用Python内置的json模块加载该文件时,模块不会自动解析指针、替换为对应实际色值,会将$ref结构当作普通字典对象保留。需要实现通用加载逻辑:JSON存在指针时自动完成解析替换,JSON不含指针时也可正常加载。
备注:JSON标准本身并未内置指针解析的原生能力。
问题复现代码
测试代码:
import json data = json.loads(open("test.json").read()) print(data) # 原生加载输出结果: # {'ColoringLight': {'bg': '#ffffff', 'fg': '#4f4c4d', 'TokenText': '#544619', 'TokenKeyword': '#2f33ff', 'TokenPunctuation': '#dd8ced', 'TokenName': '#b6c23f', 'TokenOperator': '#f6a656', 'TokenComment': '#2cbe48', 'TokenLiteralNumber': '#ab4bbe', 'TokenLiteralString': '#f45159'}, 'syntax': {'Token.Text': {'$ref': '#/ColoringLight/TokenText'}, 'Token.Keyword': {'$ref': '#/ColoringLight/TokenKeyword'}, 'Token.Punctuation': {'$ref': '#/ColoringLight/TokenPunctuation'}, 'Token.Name.Attribute': {'$ref': '#/ColoringLight/TokenName'}, 'Token.Operator': {'$ref': '#/ColoringLight/TokenOperator'}, 'Token.Comment': {'$ref': '#/ColoringLight/TokenComment'}, 'Token.Literal.Number.Bin': {'$ref': '#/ColoringLight/TokenLiteralNumber'}, 'Token.Literal.String.Affix': {'$ref': '#/ColoringLight/TokenLiteralString'}}}
对应test.json文件内容:
{ "ColoringLight": { "bg": "#ffffff", "fg": "#4f4c4d", "TokenText": "#544619", "TokenKeyword": "#2f33ff", "TokenPunctuation": "#dd8ced", "TokenName": "#b6c23f", "TokenOperator": "#f6a656", "TokenComment": "#2cbe48", "TokenLiteralNumber": "#ab4bbe", "TokenLiteralString": "#f45159" }, "syntax": { "Token.Text": {"$ref": "#/ColoringLight/TokenText"}, "Token.Keyword": {"$ref": "#/ColoringLight/TokenKeyword"}, "Token.Punctuation": {"$ref": "#/ColoringLight/TokenPunctuation"}, "Token.Name.Attribute": {"$ref": "#/ColoringLight/TokenName"}, "Token.Operator": {"$ref": "#/ColoringLight/TokenOperator"}, "Token.Comment": {"$ref": "#/ColoringLight/TokenComment"}, "Token.Literal.Number.Bin": {"$ref": "#/ColoringLight/TokenLiteralNumber"}, "Token.Literal.String.Affix": {"$ref": "#/ColoringLight/TokenLiteralString"} } }
实现方案
实现逻辑:
- 先用原生
json模块加载原始数据 - 递归遍历所有字典、列表节点,识别仅包含
$ref键的引用节点 - 解析
#/开头的本地指针路径,从根节点逐层查找对应实际值 - 将引用节点替换为实际值,非引用节点原样返回,兼容无
$ref的普通JSON场景
完整实现代码:
import json def resolve_json_refs(obj, root=None): # 首次调用时将根节点绑定为原始加载对象 if root is None: root = obj # 递归处理字典节点 if isinstance(obj, dict): # 匹配$ref引用结构 if len(obj) == 1 and "$ref" in obj: ref_path = obj["$ref"] # 处理本地JSON指针 if ref_path.startswith("#/"): path_parts = ref_path[2:].split("/") current = root for part in path_parts: current = current[part] return current # 普通字典递归处理所有值 return {key: resolve_json_refs(value, root) for key, value in obj.items()} # 递归处理列表节点 elif isinstance(obj, list): return [resolve_json_refs(item, root) for item in obj] # 基础类型(字符串、数字、布尔、None)直接返回 else: return obj # 调用示例 if __name__ == "__main__": with open("test.json", "r", encoding="utf-8") as f: raw_data = json.load(f) final_data = resolve_json_refs(raw_data) print(final_data)
效果说明
运行后syntax节点下所有引用都会被替换为ColoringLight中定义的实际色值,例如Token.Text对应值直接为#544619,不再保留$ref字典结构。如果加载的JSON中不存在任何$ref字段,函数返回结果和原生json.load完全一致,无额外副作用。
内容的提问来源于stack exchange,提问作者TRCK
相关产品推荐
相关产品推荐

