原文给出一个基于 Python 与 Slint 的 JSON 编辑助手源码,核心不是界面展示,而是 JSON 校验、格式化、压缩、树形结构构建与 Slint 属性绑定、回调机制的配合。下面按数据流、核心函数、UI 联动、错误定位和兼容性整理实现要点,适合作为中小型 JSON 调试工具、脚本辅助编辑器或桌面端开发练习的参考。
一、整体数据流
Slint 侧 MainWindow 定义 in-out property:json-text、char-count、status、tree-nodes,并声明 validate-json、format-json、minify-json、clear-all、text-changed 回调。Python 侧通过 slint.load_file 加载 main.slint,取得 MainWindow 并实例化,初始化 status 为就绪状态,error-line 和 error-col 为 -1,tree-nodes 先赋值为 slint.ListModel([]),随后尝试用默认 json-text 构建树节点。
编辑区 TextInput 使用 text <=> root.json-text 双向绑定,edited 时触发 text-changed。Python 端把 on_text_changed 赋给 ui.text_changed,文本变化后更新 json_text、char_count,并调用 validate_json 校验。校验成功时,json.loads 得到对象,再调用 build_tree 生成扁平节点列表,最后包装成 slint.ListModel 赋给 ui.tree_nodes,Slint 的 for node[idx] in root.tree-nodes 负责渲染。按钮点击则走 Slint callback,进入 Python 端 on_validate、on_format、on_minify、on_clear 等函数。
二、JSON 处理函数设计
validate_json 返回四元组 success、error_msg、line、col。空文本返回 False、JSON 为空、1、1;json.loads 成功返回 True、JSON 格式正确、-1、-1;捕获 json.JSONDecodeError 时返回 False、解析错误消息、e.lineno、e.colno;其他异常返回 False、未知错误、1、1。其中 -1 表示没有错误位置,Slint 侧只有在 error-line >= 0 时才显示行列信息。
- def validate_json(text):
- if not text.strip():
- return False, 'JSON 为空', 1, 1
- try:
- json.loads(text)
- return True, 'JSON 格式正确', -1, -1
- except json.JSONDecodeError as e:
- return False, f'解析错误: {e.msg}', e.lineno, e.colno
- except Exception as e:
- return False, f'未知错误: {str(e)}', 1, 1
复制代码
format_json 先 json.loads,再用 json.dumps(obj, indent=indent, ensure_ascii=False) 美化,indent 默认 2;失败时返回无法格式化消息并带 e.lineno。minify_json 同样先解析,再用 json.dumps(obj, separators=(',', ':'), ensure_ascii=False) 压缩空白;失败时返回无法压缩消息并带行号。ensure_ascii=False 让中文保持原样,separators 控制压缩后的键值分隔符。
get_type_name 负责把 Python 值映射成 null、boolean、number、string、array、object、unknown。判断顺序中 bool 必须放在 int/float 之前,否则 True 会被当成 number。get_preview 对 None 返回 null,对 bool 返回 true 或 false,对字符串超过 max_len=30 时截断并追加省略号,对 dict/list 返回空字符串,避免复杂类型在树节点中展示过长预览。
三、树形节点的扁平化构建
build_tree 递归返回扁平节点列表,每个节点包含 key、value-type、value-preview、depth、child-count。对象遍历 items,数组用 enumerate 生成 形式的 key;遇到 dict 或 list 子值时递归 depth + 1;child-count 只在子值是 dict/list 时取 len(v),标量为 0。Slint 不需要维护父子索引,而是通过 node.depth 乘以 16px 再加 8px 的 padding-left 表达层级。
- def build_tree(obj, key='', depth=0):
- nodes = []
- if isinstance(obj, dict):
- for k, v in obj.items():
- child_count = len(v) if isinstance(v, (dict, list)) else 0
- nodes.append({
- 'key': k,
- 'value-type': get_type_name(v),
- 'value-preview': get_preview(v),
- 'depth': depth,
- 'child-count': child_count,
- })
- if isinstance(v, (dict, list)):
- nodes.extend(build_tree(v, k, depth + 1))
- elif isinstance(obj, list):
- for i, v in enumerate(obj):
- child_count = len(v) if isinstance(v, (dict, list)) else 0
- nodes.append({
- 'key': f'[{i}]',
- 'value-type': get_type_name(v),
- 'value-preview': get_preview(v),
- 'depth': depth,
- 'child-count': child_count,
- })
- if isinstance(v, (dict, list)):
- nodes.extend(build_tree(v, f'[{i}]', depth + 1))
- return nodes
复制代码
四、Slint 侧联动与回调绑定
Slint 侧定义 TreeNode 和 StatusInfo 结构,TreeNode 对应树节点字段,StatusInfo 对应 message、is-error、error-line、error-col。ActionButton 组件封装按钮文字、背景色、悬停色和 clicked 回调;TreeNodeRow 根据 node.value-type 选择符号和颜色,显示 key、value-preview 和 child-count,并用 is-last 控制最后一行分隔线。
Python 端创建 MainWindow 后,把函数对象直接赋给 ui.validate_json、ui.format_json、ui.minify_json、ui.clear_all、ui.text_changed。on_format 成功时把格式化结果写回 ui.json_text,更新 char_count,status 设为格式化成功,并尝试重新 json.loads 后刷新树节点;失败时 status.is-error 为 True,message 为错误结果。on_minify 成功时写回压缩结果并更新字符数,但原文代码没有在压缩成功后重新构建树节点。on_clear 清空文本、字符数、状态和 tree_nodes。
on_text_changed 是输入时的主链路:更新 ui.json_text 与 ui.char_count;文本非空时调用 validate_json;成功则 json.loads 并重新 build_tree,失败则只更新状态,不强制清空已有树节点。on_validate 按钮成功时会刷新树节点,失败时保留现有树节点。状态反馈区根据 is-error 切换颜色和符号,底部只有在 error-line >= 0 时显示错误位置。
五、错误定位与排查流程
排查时先看 status.message,再看控制台输出的 [验证]、[格式化]、[压缩]、[清空] 日志。空输入会得到 JSON 为空,行列均为 1;解析失败会使用 JSONDecodeError 的 lineno 和 colno,适合直接定位到编辑区行列;格式化或压缩失败只返回带行号的消息,error-line 为 -1,因此 UI 不会显示行列。若 main.slint 编译失败,main.py 会打印 e.args[1] 中的诊断信息并退出,应先检查 slint.load_file 的路径和 main.slint 语法。默认树构建、格式化后刷新树等位置存在 try/except pass,失败时界面不一定报错,复现问题时要同时查看 Python 控制台。
六、性能与兼容性细节
当前实现每次 text-changed 都会完整执行 json.loads、validate_json、build_tree,并重新创建 slint.ListModel;输入频繁或 JSON 很大时,重复解析和全量建树会放大开销。基于这一实现可考虑输入防抖、只在需要时重建树、限制超大数组或对象的展示量、保留字符串预览上限 max_len=30。依赖方面,代码使用 Python 标准库 json、pathlib 和 Slint Python 绑定,main.slint 与 main.py 放在同一目录,通过 Path(__file__).parent.resolve() 定位资源。Slint 侧 ListModel 接收节点列表,TextInput 双向绑定 json-text,ScrollView 与 Flickable 分别承载编辑区和树形区。原文未给出安装命令和版本号,复现时应按本机 Slint Python 绑定环境调整。 |