文章目录
- 主窗口继承自 QMainWindow: class JsonViewer(QMainWindow): def __init__(self): super().__init__() self.data = None self.current_path = None self.init_ui() self.data:用来保存当前加载的 JSON 对象 self.current_path:记录当前 JSON 文件路径 init_ui():负责创建和布局所有控件,并设置样式
- def init_ui(self): self.setWindowTitle(“JSON快速查看器”) self.resize(800, 520) self.setStyleSheet( “”” QWidget { background-color: #f5f5f7; font-family: “Microsoft YaHei”; font-size: 10pt; } QPushButton { background-color: #0078d7; color: white; border-radius: 4px; padding: 6px 14px; } QPushButton:hover { background-color: #0a84ff; } QPushButton:pressed { background-color: #005a9e; } QLineEdit, QTextEdit { background-color: #ffffff; border: 1px solid #d0d0d5; border-radius: 4px; } QLineEdit:focus, QTextEdit:focus { border: 1px solid #0a84ff; } “”” ) 这里用了一段简单的 Qt 样式表(类似 CSS): 全局字体:微软雅黑 10pt 背景色:淡灰色 #f5f5f7 按钮:蓝色背景 + 白字 + 圆角 + 悬停 / 按下态 输入框和文本框:白色背景 + 边框 + 聚焦时高亮 通过一次性设置 QWidget 和各控件的样式,可以避免对每个控件逐个设置属性,代码更简洁,视觉也更统一。
- central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout() central_widget.setLayout(main_layout) main_layout.setContentsMargins(20, 18, 20, 18) main_layout.setSpacing(12) self.info_label = QLabel(“JSON 快速查看器”) self.info_label.setAlignment(Qt.AlignCenter) title_font = self.info_label.font() title_font.setPointSize(14) title_font.setBold(True) self.info_label.setFont(title_font) main_layout.addWidget(self.info_label) subtitle = QLabel(“拖入 JSON 文件到窗口,或点击右侧按钮选择文件”) subtitle.setAlignment(Qt.AlignCenter) subtitle.setStyleSheet(“color: #666666;”) main_layout.addWidget(subtitle) 纵向布局 QVBoxLayout:从上到下堆叠各个区域 主标题用 14 号加粗字体,居中显示 副标题用浅灰色,提示使用方法
- file_layout = QHBoxLayout() self.file_label = QLabel(“未选择文件”) self.file_label.setMinimumWidth(300) self.file_label.setTextInteractionFlags(Qt.TextSelectableByMouse) self.file_label.setStyleSheet(“color: #555555;”) open_button = QPushButton(“打开JSON”) open_button.clicked.connect(self.open_file) file_layout.addWidget(self.file_label) file_layout.addWidget(open_button) main_layout.addLayout(file_layout) 设计要点: 使用水平布局 QHBoxLayout,左边是文件路径,右边是按钮 文件路径标签支持鼠标选中,方便复制路径 初始提示为「未选择文件」
- key_layout = QHBoxLayout() key_label = QLabel(“KEY:”) key_label.setStyleSheet(“color: #333333;”) self.key_edit = QLineEdit() self.key_edit.setPlaceholderText(“例如: user.name 或 items.0.id”) key_font = self.key_edit.font() key_font.setPointSize(10) self.key_edit.setFont(key_font) search_button = QPushButton(“查询”) search_button.clicked.connect(self.lookup_key) self.key_edit.returnPressed.connect(self.lookup_key) key_layout.addWidget(key_label) key_layout.addWidget(self.key_edit) key_layout.addWidget(search_button) main_layout.addLayout(key_layout) 细节设计: 提示语直接告诉用户 KEY 的写法示例 支持两种触发方式: 点击「查询」按钮 在输入框里按 Enter(returnPressed 信号)
- self.result_edit = QTextEdit() self.result_edit.setReadOnly(True) result_font = self.result_edit.font() result_font.setFamily(“Consolas”) result_font.setPointSize(10) self.result_edit.setFont(result_font) main_layout.addWidget(self.result_edit) self.status_label = QLabel(“”) self.status_label.setStyleSheet(“color: #2e7d32;”) main_layout.addWidget(self.status_label) 结果用 QTextEdit 展示,适合显示多行 JSON 使用等宽字体 Consolas,方便对齐与阅读 底部 status_label 用来显示提示信息: JSON 加载成功 未找到对应的值 请先加载 JSON 文件等等
- self.setAcceptDrops(True) 然后重写两个事件函数: def dragEnterEvent(self, event): if event.mimeData().hasUrls(): for url in event.mimeData().urls(): path = url.toLocalFile() if path.lower().endswith(“.json”): event.acceptProposedAction() return event.ignore() 逻辑: 拖入的内容如果包含文件 URL 且其中有 .json 后缀的文件 则接受这次拖拽,否则忽略 真正的放下事件: def dropEvent(self, event): for url in event.mimeData().urls(): path = url.toLocalFile() if path.lower().endswith(“.json”): self.load_json(path) break 取第一个 .json 文件的路径 调用 load_json 进行加载
- def open_file(self): path, _ = QFileDialog.getOpenFileName( self, “选择 JSON 文件”, “”, “JSON Files (*.json);;All Files (*)”, ) if path: self.load_json(path) 使用 QFileDialog.getOpenFileName: 默认过滤器是 .json 文件 也可以切换为「所有文件」打开
- def load_json(self, path): try: with open(path, “r”, encoding=”utf-8″) as f: self.data = json.load(f) self.current_path = path self.file_label.setText(path) self.status_label.setText(“JSON 加载成功”) self.result_edit.clear() except Exception as e: self.data = None self.current_path = None self.file_label.setText(“未选择文件”) self.status_label.setText(“加载失败: {0}”.format(e)) self.result_edit.clear() 成功: json.load 解析为 Python 对象(dict / list) 更新当前路径显示 清空结果区域 状态显示为「JSON 加载成功」 失败: 清空状态 文件标签重置 状态栏显示错误原因
- def lookup_key(self): if self.data is None: self.status_label.setText(“请先加载 JSON 文件”) return key_path = self.key_edit.text().strip() if not key_path: self.status_label.setText(“请输入 KEY”) return try: value = self.get_value_by_path(self.data, key_path) except (KeyError, IndexError, TypeError): self.status_label.setText(“未找到对应的值”) self.result_edit.clear() return if isinstance(value, (dict, list)): text = json.dumps(value, ensure_ascii=False, indent=2) else: text = str(value) self.result_edit.setPlainText(text) self.status_label.setText(“查询成功”) 逻辑分几步: 检查是否已加载 JSON 检查 KEY 是否为空 调用 get_value_by_path 获取值,捕获各种失败情况 如果结果是 dict 或 list,用 json.dumps 美化输出 否则直接转成字符串显示
- def get_value_by_path(self, data, path): if not path: return data current = data parts = path.split(“.”) for part in parts: if isinstance(current, list) and part.isdigit(): index = int(part) current = current[index] elif isinstance(current, dict): if part in current: current = current[part] else: raise KeyError(part) else: raise KeyError(part) return current 这个函数支持的语法: 对象键: user.name 会依次访问 data["user"]["name"] 列表下标: items.0 相当于 data["items"][0] items.0.id 相当于 data["items"][0]["id"] 实现方式: 把路径按 . 切分为列表 ["items", "0", "id"] 从根对象开始,逐层向下走 当前对象是 list 且当前 part 是数字时: 解析成 int 下标,访问对应元素 当前对象是 dict 时: 直接按照键访问 如果中途类型不匹配或键不存在,就抛异常,由调用方统一处理为「未找到对应的值」 这个函数逻辑简单清晰,但已经覆盖了绝大部分日常 JSON 调试的需求。
目录
- 一、需求与效果
- 二、项目结构与入口
- 三、窗口与整体样式设计
- 1. 主窗口类与基础设置
- 2. 设置窗口大小与全局样式
- 四、布局与控件拆解
- 1. 主布局与标题区
- 2. 文件选择行(显示路径 + 打开按钮)
- 3. KEY 输入行(KEY + 输入框 + 查询按钮)
- 4. 结果显示区与状态栏
- 五、拖拽与打开 JSON 文件
- 1. 开启拖拽支持
- 2. 通过文件选择框打开 JSON
- 3. 加载 JSON 文件
- 六、KEY 路径解析与查询逻辑
- 1. 查询入口函数
- 2. 路径解析核心函数
- 七、少量代码实现一个高频小工具
- 八、小结
最近写了一个小工具:只要拖入或打开一个 JSON 文件,输入 KEY,就能快速查看对应的值。本文以 d:测试Github项目59-JOSN快速查看器main.py 为例,完整拆解这个 PyQt5 小工具的实现思路和关键代码。
需求非常简单:
- 支持从文件拖拽或文件选择框加载 JSON 文件
- 输入 KEY,快速显示对应的值
- KEY 支持「点语法」和列表下标:
nameuser.nameitems.0.id
- 界面要尽量简洁,美观,适合日常调试 JSON 时快速使用
最终效果:
- 顶部标题 + 提示语,说明工具用途和操作方式
- 中间一行显示当前 JSON 文件路径 + 打开按钮
- 一行 KEY 输入 + 查询按钮
- 下方大区域显示查询结果(支持格式化 JSON)
- 底部状态栏显示操作结果提示(成功 / 失败)
本工具的主体代码全部在一个文件中:
- 主文件:
main.py - 主类:
JsonViewer(QMainWindow) - 入口函数:
main()
入口函数代码:
def main():
app = QApplication(sys.argv)
viewer = JsonViewer()
viewer.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
这是典型的 PyQt5 应用启动方式:
- 创建
QApplication实例 - 实例化主窗口
JsonViewer - 显示窗口
- 进入事件循环
主窗口继承自 QMainWindow:
class JsonViewer(QMainWindow):
def __init__(self):
super().__init__()
self.data = None
self.current_path = None
self.init_ui()
self.data:用来保存当前加载的 JSON 对象self.current_path:记录当前 JSON 文件路径init_ui():负责创建和布局所有控件,并设置样式
def init_ui(self):
self.setWindowTitle("JSON快速查看器")
self.resize(800, 520)
self.setStyleSheet(
"""
QWidget {
background-color: #f5f5f7;
font-family: "Microsoft YaHei";
font-size: 10pt;
}
QPushButton {
background-color: #0078d7;
color: white;
border-radius: 4px;
padding: 6px 14px;
}
QPushButton:hover {
background-color: #0a84ff;
}
QPushButton:pressed {
background-color: #005a9e;
}
QLineEdit, QTextEdit {
background-color: #ffffff;
border: 1px solid #d0d0d5;
border-radius: 4px;
}
QLineEdit:focus, QTextEdit:focus {
border: 1px solid #0a84ff;
}
"""
)
这里用了一段简单的 Qt 样式表(类似 CSS):
- 全局字体:微软雅黑 10pt
- 背景色:淡灰色
#f5f5f7 - 按钮:蓝色背景 + 白字 + 圆角 + 悬停 / 按下态
- 输入框和文本框:白色背景 + 边框 + 聚焦时高亮
通过一次性设置 QWidget 和各控件的样式,可以避免对每个控件逐个设置属性,代码更简洁,视觉也更统一。
central_widget = QWidget()
self.setCentralWidget(central_widget)
main_layout = QVBoxLayout()
central_widget.setLayout(main_layout)
main_layout.setContentsMargins(20, 18, 20, 18)
main_layout.setSpacing(12)
self.info_label = QLabel("JSON 快速查看器")
self.info_label.setAlignment(Qt.AlignCenter)
title_font = self.info_label.font()
title_font.setPointSize(14)
title_font.setBold(True)
self.info_label.setFont(title_font)
main_layout.addWidget(self.info_label)
subtitle = QLabel("拖入 JSON 文件到窗口,或点击右侧按钮选择文件")
subtitle.setAlignment(Qt.AlignCenter)
subtitle.setStyleSheet("color: #666666;")
main_layout.addWidget(subtitle)
- 纵向布局
QVBoxLayout:从上到下堆叠各个区域
- 主标题用 14 号加粗字体,居中显示
- 副标题用浅灰色,提示使用方法
QVBoxLayout:从上到下堆叠各个区域
file_layout = QHBoxLayout()
self.file_label = QLabel("未选择文件")
self.file_label.setMinimumWidth(300)
self.file_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
self.file_label.setStyleSheet("color: #555555;")
open_button = QPushButton("打开JSON")
open_button.clicked.connect(self.open_file)
file_layout.addWidget(self.file_label)
file_layout.addWidget(open_button)
main_layout.addLayout(file_layout)
设计要点:
- 使用水平布局
QHBoxLayout,左边是文件路径,右边是按钮 - 文件路径标签支持鼠标选中,方便复制路径
- 初始提示为「未选择文件」
key_layout = QHBoxLayout()
key_label = QLabel("KEY:")
key_label.setStyleSheet("color: #333333;")
self.key_edit = QLineEdit()
self.key_edit.setPlaceholderText("例如: user.name 或 items.0.id")
key_font = self.key_edit.font()
key_font.setPointSize(10)
self.key_edit.setFont(key_font)
search_button = QPushButton("查询")
search_button.clicked.connect(self.lookup_key)
self.key_edit.returnPressed.connect(self.lookup_key)
key_layout.addWidget(key_label)
key_layout.addWidget(self.key_edit)
key_layout.addWidget(search_button)
main_layout.addLayout(key_layout)
细节设计:
- 提示语直接告诉用户 KEY 的写法示例
- 支持两种触发方式:
- 点击「查询」按钮
- 在输入框里按 Enter(
returnPressed信号)
self.result_edit = QTextEdit()
self.result_edit.setReadOnly(True)
result_font = self.result_edit.font()
result_font.setFamily("Consolas")
result_font.setPointSize(10)
self.result_edit.setFont(result_font)
main_layout.addWidget(self.result_edit)
self.status_label = QLabel("")
self.status_label.setStyleSheet("color: #2e7d32;")
main_layout.addWidget(self.status_label)
- 结果用
QTextEdit 展示,适合显示多行 JSON
- 使用等宽字体
Consolas,方便对齐与阅读
- 底部
status_label 用来显示提示信息:
- JSON 加载成功
- 未找到对应的值
- 请先加载 JSON 文件
等等
QTextEdit 展示,适合显示多行 JSONConsolas,方便对齐与阅读status_label 用来显示提示信息:
- JSON 加载成功
- 未找到对应的值
- 请先加载 JSON 文件
等等
self.setAcceptDrops(True)
然后重写两个事件函数:
def dragEnterEvent(self, event):
if event.mimeData().hasUrls():
for url in event.mimeData().urls():
path = url.toLocalFile()
if path.lower().endswith(".json"):
event.acceptProposedAction()
return
event.ignore()
逻辑:
- 拖入的内容如果包含文件 URL
- 且其中有
.json后缀的文件 - 则接受这次拖拽,否则忽略
真正的放下事件:
def dropEvent(self, event):
for url in event.mimeData().urls():
path = url.toLocalFile()
if path.lower().endswith(".json"):
self.load_json(path)
break
- 取第一个
.json文件的路径 - 调用
load_json进行加载
def open_file(self):
path, _ = QFileDialog.getOpenFileName(
self,
"选择 JSON 文件",
"",
"JSON Files (*.json);;All Files (*)",
)
if path:
self.load_json(path)
使用 QFileDialog.getOpenFileName:
- 默认过滤器是
.json文件 - 也可以切换为「所有文件」打开
def load_json(self, path):
try:
with open(path, "r", encoding="utf-8") as f:
self.data = json.load(f)
self.current_path = path
self.file_label.setText(path)
self.status_label.setText("JSON 加载成功")
self.result_edit.clear()
except Exception as e:
self.data = None
self.current_path = None
self.file_label.setText("未选择文件")
self.status_label.setText("加载失败: {0}".format(e))
self.result_edit.clear()
- 成功:
json.load 解析为 Python 对象(dict / list)
- 更新当前路径显示
- 清空结果区域
- 状态显示为「JSON 加载成功」
- 失败:
- 清空状态
- 文件标签重置
- 状态栏显示错误原因
json.load解析为 Python 对象(dict / list)- 更新当前路径显示
- 清空结果区域
- 状态显示为「JSON 加载成功」
- 清空状态
- 文件标签重置
- 状态栏显示错误原因
def lookup_key(self):
if self.data is None:
self.status_label.setText("请先加载 JSON 文件")
return
key_path = self.key_edit.text().strip()
if not key_path:
self.status_label.setText("请输入 KEY")
return
try:
value = self.get_value_by_path(self.data, key_path)
except (KeyError, IndexError, TypeError):
self.status_label.setText("未找到对应的值")
self.result_edit.clear()
return
if isinstance(value, (dict, list)):
text = json.dumps(value, ensure_ascii=False, indent=2)
else:
text = str(value)
self.result_edit.setPlainText(text)
self.status_label.setText("查询成功")
逻辑分几步:
- 检查是否已加载 JSON
- 检查 KEY 是否为空
- 调用
get_value_by_path获取值,捕获各种失败情况 - 如果结果是
dict或list,用json.dumps美化输出 - 否则直接转成字符串显示
def get_value_by_path(self, data, path):
if not path:
return data
current = data
parts = path.split(".")
for part in parts:
if isinstance(current, list) and part.isdigit():
index = int(part)
current = current[index]
elif isinstance(current, dict):
if part in current:
current = current[part]
else:
raise KeyError(part)
else:
raise KeyError(part)
return current
这个函数支持的语法:
- 对象键:
user.name会依次访问data["user"]["name"]
- 列表下标:
items.0相当于data["items"][0]items.0.id相当于data["items"][0]["id"]
实现方式:
- 把路径按
.切分为列表["items", "0", "id"] - 从根对象开始,逐层向下走
- 当前对象是 list 且当前 part 是数字时:
- 解析成 int 下标,访问对应元素
- 当前对象是 dict 时:
- 直接按照键访问
- 如果中途类型不匹配或键不存在,就抛异常,由调用方统一处理为「未找到对应的值」
这个函数逻辑简单清晰,但已经覆盖了绝大部分日常 JSON 调试的需求。
整个 main.py 代码量不大,却完成了:
- 一个小而美的 PyQt5 窗口应用
- 支持拖拽 / 打开 JSON 文件
- 支持 KEY 路径解析(对象 + 列表)
- 自适应格式化输出结果
- 简单但实用的状态提示和 UI 美化
如果需要扩展,这个工具还可以进一步完善:
- 支持
a[0].b这种带中括号的路径语法 - 增加「历史 KEY」下拉列表,快速重复查询
- 添加「复制结果」按钮,一键复制查询结果
- 增加「JSON 树形视图」,可视化浏览整个结构
这篇文章从需求出发,完整拆解了 main.py 中的实现:
- 如何用 PyQt5 快速搭建一个桌面小工具
- 如何用样式表和字体调整让界面更友好
- 如何通过拖拽和文件选择加载 JSON
- 如何用简单的「点语法 + 数字下标」解析 JSON 路径
如果你也经常需要调试接口或查看复杂 JSON,不妨参考这个实现,按自己的习惯继续扩展,让它成为你日常开发中的一个趁手小工具。
以上就是Python使用PyQt5打造一个JSON快速查看器的详细内容,更多关于PyQt5 JSON快速查看器的资料请关注风君子博客其它相关文章!
您可能感兴趣的文章:
- 使用Python和PyQt5开发一个多功能音乐播放器
- 基于Python+PyQt5+Pandas编写的带图形界面的Excel合并工具
- Python+PyQt5开发一个Windows EXE程序在线更新工具
- PyQt5实现多界面自由切换的完整项目实践指南
- Python基于PyQt5和openpyxl实现Excel单元格合并工具