import json
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, simpledialog
import pyperclip
import os
import sqlite3
from abc import ABC, abstractmethod
import pymongo # 需要安装: pip install pymongo
from bson import json_util
class DatabaseAdapter(ABC):
"""数据库适配器抽象基类"""
@abstractmethod
def connect(self, connection_string):
pass
@abstractmethod
def get_json_data(self, identifier):
"""获取JSON数据"""
pass
@abstractmethod
def close(self):
pass
class SQLiteAdapter(DatabaseAdapter):
"""SQLite3数据库适配器"""
def __init__(self):
self.conn = None
self.cursor = None
def connect(self, db_path):
"""连接SQLite数据库"""
if not os.path.exists(db_path):
raise FileNotFoundError(f"数据库文件不存在: {db_path}")
self.conn = sqlite3.connect(db_path)
self.cursor = self.conn.cursor()
return True
def get_json_data(self, table_name):
"""从指定表获取JSON数据"""
if not self.conn:
raise ConnectionError("数据库未连接")
# 检查表是否存在
self.cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table_name,))
if not self.cursor.fetchone():
raise ValueError(f"表 '{table_name}' 不存在")
# 检查payload列是否存在
self.cursor.execute(f"PRAGMA table_info({table_name})")
columns = [col[1] for col in self.cursor.fetchall()]
if 'payload' not in columns:
raise ValueError(f"表 '{table_name}' 中未找到 'payload' 列")
# 获取JSON数据
self.cursor.execute(f"SELECT payload FROM {table_name}")
result = self.cursor.fetchone()
if not result:
raise ValueError(f"表 '{table_name}' 中没有数据")
return json.loads(result[0])
def close(self):
if self.conn:
self.conn.close()
class MongoDBAdapter(DatabaseAdapter):
"""MongoDB数据库适配器(未来扩展)"""
def __init__(self):
self.client = None
self.db = None
def connect(self, connection_string):
"""连接MongoDB数据库"""
self.client = pymongo.MongoClient(connection_string)
# 这里可以添加选择数据库的逻辑
# 例如: self.db = self.client['database_name']
return True
def get_json_data(self, collection_name):
"""从指定集合获取JSON数据"""
if not self.client:
raise ConnectionError("数据库未连接")
# 这里可以添加具体的查询逻辑
# 例如: document = self.db[collection_name].find_one()
# return json.loads(json_util.dumps(document))
return {"message": "MongoDB支持将在未来版本中实现"}
def close(self):
if self.client:
self.client.close()
class JSONTreeViewer:
def __init__(self, root):
self.root = root
self.root.title("JSON树形查看器")
self.root.geometry("900x650")
# 创建菜单栏
menubar = tk.Menu(root)
# 文件菜单
filemenu = tk.Menu(menubar, tearoff=0)
filemenu.add_command(label="打开JSON文件", command=self.load_json)
filemenu.add_separator()
filemenu.add_command(label="退出", command=root.quit)
menubar.add_cascade(label="文件", menu=filemenu)
# 数据库菜单
dbmenu = tk.Menu(menubar, tearoff=0)
dbmenu.add_command(label="从SQLite加载", command=self.load_from_sqlite)
dbmenu.add_command(label="从MongoDB加载", command=self.load_from_mongodb)
menubar.add_cascade(label="数据库", menu=dbmenu)
root.config(menu=menubar)
# 创建树形视图
self.tree = ttk.Treeview(root, columns=("type", "value"), show="tree")
self.tree.heading("#0", text="键")
self.tree.heading("type", text="类型")
self.tree.heading("value", text="值")
# 添加滚动条
scrollbar = ttk.Scrollbar(root, orient="vertical", command=self.tree.yview)
self.tree.configure(yscrollcommand=scrollbar.set)
scrollbar.pack(side="right", fill="y")
self.tree.pack(fill="both", expand=True)
# 状态栏
self.status_var = tk.StringVar()
self.status_var.set("就绪")
status_bar = ttk.Label(root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
status_bar.pack(side=tk.BOTTOM, fill=tk.X)
# 绑定双击事件
self.tree.bind("<Double-1>", self.on_double_click)
# 创建右键菜单
self.context_menu = tk.Menu(root, tearoff=0)
self.context_menu.add_command(label="复制JSON路径", command=self.copy_json_path)
self.context_menu.add_separator()
self.context_menu.add_command(label="展开所有", command=self.expand_all)
self.context_menu.add_command(label="折叠所有", command=self.collapse_all)
# 绑定右键事件
self.tree.bind("<Button-3>", self.show_context_menu)
# 存储节点数据
self.node_data = {}
self.file_path = ""
self.db_adapter = None
# 存储节点路径
self.node_paths = {}
def update_status(self, message):
"""更新状态栏信息"""
self.status_var.set(message)
self.root.update_idletasks()
def load_json(self):
"""打开并加载JSON文件"""
file_path = filedialog.askopenfilename(
filetypes=[("JSON文件", "*.json"), ("所有文件", "*.*")]
)
if not file_path:
return
self.file_path = file_path
self.update_status(f"加载文件: {file_path}")
try:
with open(file_path, 'r', encoding='utf-8') as file:
data = json.load(file)
self.display_json(data, f"文件: {os.path.basename(file_path)}")
except Exception as e:
messagebox.showerror("错误", f"加载JSON文件失败:\n{str(e)}")
self.update_status(f"错误: {str(e)}")
def load_from_sqlite(self):
"""从SQLite数据库加载JSON数据"""
db_path = filedialog.askopenfilename(
filetypes=[("SQLite数据库", "*.db *.sqlite *.sqlite3"), ("所有文件", "*.*")]
)
if not db_path:
return
# 获取表名(基于文件名)
filename = os.path.basename(db_path)
table_name = os.path.splitext(filename)[0]
# 允许用户修改表名
new_table_name = simpledialog.askstring(
"表名选择",
f"默认表名基于文件名: {table_name}\n输入表名或使用默认值:",
initialvalue=table_name
)
if new_table_name is None: # 用户取消
return
table_name = new_table_name.strip()
if not table_name:
messagebox.showwarning("警告", "表名不能为空")
return
self.update_status(f"连接数据库: {db_path}, 表名: {table_name}")
try:
# 创建并连接数据库适配器
adapter = SQLiteAdapter()
adapter.connect(db_path)
self.db_adapter = adapter
# 获取JSON数据
json_data = adapter.get_json_data(table_name)
self.display_json(json_data, f"数据库: {os.path.basename(db_path)} → 表: {table_name}")
except Exception as e:
messagebox.showerror("数据库错误", f"加载数据库失败:\n{str(e)}")
self.update_status(f"错误: {str(e)}")
finally:
if adapter:
adapter.close()
def load_from_mongodb(self):
"""从MongoDB数据库加载JSON数据(示例)"""
connection_string = simpledialog.askstring(
"MongoDB连接",
"输入MongoDB连接字符串 (例如: mongodb://localhost:27017):"
)
if not connection_string:
return
collection_name = simpledialog.askstring(
"集合名称",
"输入集合名称:"
)
if not collection_name:
return
self.update_status(f"连接MongoDB: {connection_string}, 集合: {collection_name}")
try:
# 创建并连接数据库适配器
adapter = MongoDBAdapter()
adapter.connect(connection_string)
self.db_adapter = adapter
# 获取JSON数据
json_data = adapter.get_json_data(collection_name)
self.display_json(json_data, f"MongoDB → 集合: {collection_name}")
except Exception as e:
messagebox.showerror("数据库错误", f"连接MongoDB失败:\n{str(e)}")
self.update_status(f"错误: {str(e)}")
finally:
if adapter:
adapter.close()
def display_json(self, data, source_info=""):
"""显示JSON数据"""
self.tree.delete(*self.tree.get_children())
self.node_data.clear()
self.node_paths.clear()
root_id = self.add_node("", "root", data, "")
self.tree.item(root_id, open=True)
# 更新状态栏
if source_info:
self.update_status(f"已加载数据 - {source_info}")
else:
self.update_status("已加载JSON数据")
def add_node(self, parent, key, value, parent_path):
"""添加节点到树形视图"""
node_id = self.tree.insert(parent, "end", text=str(key), values=(type(value).__name__,))
# 构建当前节点的完整路径
if parent_path == "" or parent_path == "/":
current_path = f"/{key}"
else:
current_path = f"{parent_path}/{key}"
self.node_paths[node_id] = current_path
if isinstance(value, dict):
self.tree.set(node_id, "value", f"({len(value)} 项)")
self.node_data[node_id] = ("dict", value)
for k, v in value.items():
self.add_node(node_id, k, v, current_path)
elif isinstance(value, list):
self.tree.set(node_id, "value", f"[{len(value)} 项]")
self.node_data[node_id] = ("list", value)
for i, item in enumerate(value):
self.add_node(node_id, f"[{i}]", item, current_path)
else: # 基本数据类型
self.tree.set(node_id, "value", str(value))
self.node_data[node_id] = ("leaf", value)
return node_id
def on_double_click(self, event):
"""处理双击事件 - 展开/折叠节点"""
item = self.tree.identify("item", event.x, event.y)
if item and item in self.node_data:
node_type, data = self.node_data[item]
# 如果是已折叠的字典/列表节点,展开它
if not self.tree.item(item, "open"):
if node_type in ("dict", "list"):
self.tree.item(item, open=True)
else:
# 如果已展开,则折叠
self.tree.item(item, open=False)
def show_context_menu(self, event):
"""显示右键上下文菜单"""
item = self.tree.identify("item", event.x, event.y)
if item:
# 选中当前项
self.tree.selection_set(item)
# 在鼠标位置显示菜单
self.context_menu.post(event.x_root, event.y_root)
def copy_json_path(self):
"""复制选中节点的JSON路径到剪贴板"""
selected_items = self.tree.selection()
if not selected_items:
return
item = selected_items[0]
if item in self.node_paths:
path = self.node_paths[item]
pyperclip.copy(path)
self.update_status(f"已复制路径到剪贴板: {path}")
messagebox.showinfo("复制成功", f"已复制路径到剪贴板:\n{path}")
else:
messagebox.showerror("错误", "无法获取路径")
def expand_all(self):
"""展开所有节点"""
for node_id in self.tree.get_children():
self.expand_recursive(node_id)
def collapse_all(self):
"""折叠所有节点"""
for node_id in self.tree.get_children():
self.collapse_recursive(node_id)
def expand_recursive(self, node_id):
"""递归展开节点"""
self.tree.item(node_id, open=True)
for child_id in self.tree.get_children(node_id):
self.expand_recursive(child_id)
def collapse_recursive(self, node_id):
"""递归折叠节点"""
self.tree.item(node_id, open=False)
for child_id in self.tree.get_children(node_id):
self.collapse_recursive(child_id)
if __name__ == "__main__":
root = tk.Tk()
app = JSONTreeViewer(root)
root.mainloop()
分析这个程序的结构和作用,准备进行功能的升级
最新发布