Python
import tkinter as tk
from tkinter import messagebox, filedialog, scrolledtext
import json
import re
class SyntaxCorrector:
def __init__(self, master):
self.master = master
self.master.title("Advanced JSON Syntax Corrector")
self.master.geometry("700x500")
# UI Elements
self.label = tk.Label(master, text="Select a file to check and fix JSON syntax errors:")
self.label.pack(pady=10)
self.select_button = tk.Button(master, text="Select File", command=self.select_file)
self.select_button.pack(pady=5)
self.file_info = tk.Label(master, text="No file selected", fg="gray")
self.file_info.pack(pady=5)
# Error display area
self.error_frame = tk.LabelFrame(master, text="Errors Found")
self.error_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
self.error_text = scrolledtext.ScrolledText(self.error_frame, height=10, wrap=tk.WORD)
self.error_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
# Fix options frame
self.options_frame = tk.Frame(master)
self.options_frame.pack(fill=tk.X, padx=10, pady=5)
self.preserve_structure_var = tk.BooleanVar(value=True)
self.preserve_check = tk.Checkbutton(self.options_frame, text="Preserve original structure",
variable=self.preserve_structure_var)
self.preserve_check.pack(side=tk.LEFT, padx=5)
self.backup_var = tk.BooleanVar(value=True)
self.backup_check = tk.Checkbutton(self.options_frame, text="Create backup",
variable=self.backup_var)
self.backup_check.pack(side=tk.LEFT, padx=5)
# Buttons
self.button_frame = tk.Frame(master)
self.button_frame.pack(fill=tk.X, padx=10, pady=10)
self.fix_button = tk.Button(self.button_frame, text="Fix Errors", command=self.fix_errors,
state=tk.DISABLED, bg="#4CAF50", fg="white")
self.fix_button.pack(side=tk.LEFT, padx=5)
self.view_button = tk.Button(self.button_frame, text="View Original", command=self.view_original,
state=tk.DISABLED)
self.view_button.pack(side=tk.LEFT, padx=5)
self.status_label = tk.Label(master, text="", fg="blue")
self.status_label.pack(pady=5)
# Properties
self.file_path = None
self.errors = []
self.content = ""
def select_file(self):
self.file_path = filedialog.askopenfilename(filetypes=[("JSON Files", "*.json"), ("All Files", "*.*")])
if not self.file_path:
return
self.status_label.config(text="Checking for errors...")
self.file_info.config(text=f"File: {self.file_path}")
self.master.update_idletasks()
try:
with open(self.file_path, "r", encoding="utf-8") as file:
self.content = file.read()
except UnicodeDecodeError:
try:
with open(self.file_path, "r", encoding="latin-1") as file:
self.content = file.read()
except Exception as e:
messagebox.showerror("Error", f"Failed to open file: {str(e)}")
self.status_label.config(text="Error opening file")
return
self.errors = self.check_json_syntax(self.content)
self.display_errors()
def check_json_syntax(self, content):
errors = []
try:
json.loads(content)
except json.JSONDecodeError as e:
line_col = f"Line {e.lineno}, Column {e.colno}"
error_message = f"{line_col}: {e.msg}"
# Extract the problematic line and highlight the issue
lines = content.split('\n')
if 0 <= e.lineno - 1 < len(lines):
error_line = lines[e.lineno - 1]
context = f"Context: {error_line}\n{' ' * (e.colno - 1)}^ Error might be here"
else:
context = "Could not extract context"
errors.append({
"message": error_message,
"context": context,
"line": e.lineno,
"column": e.colno,
"error": str(e)
})
return errors
def display_errors(self):
self.error_text.delete(1.0, tk.END)
if not self.errors:
self.error_text.insert(tk.END, "No syntax errors found! Your JSON is valid.")
self.status_label.config(text="File is valid JSON")
self.fix_button.config(state=tk.DISABLED)
self.view_button.config(state=tk.NORMAL)
else:
for i, error in enumerate(self.errors, 1):
self.error_text.insert(tk.END, f"Error {i}: {error['message']}\n")
self.error_text.insert(tk.END, f"{error['context']}\n\n")
self.status_label.config(text=f"Found {len(self.errors)} error(s). Click 'Fix Errors' to attempt repair.")
self.fix_button.config(state=tk.NORMAL)
self.view_button.config(state=tk.NORMAL)
def view_original(self):
if not self.file_path:
return
view_window = tk.Toplevel(self.master)
view_window.title(f"Original JSON: {self.file_path}")
view_window.geometry("700x500")
text_area = scrolledtext.ScrolledText(view_window, wrap=tk.WORD)
text_area.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
text_area.insert(tk.END, self.content)
text_area.config(state=tk.DISABLED)
def fix_errors(self):
if not self.file_path:
return
fixed_content = self.fix_json(self.content)
# Create backup if requested
if self.backup_var.get():
import shutil
backup_file = f"{self.file_path}.bak"
try:
shutil.copy2(self.file_path, backup_file)
self.status_label.config(text=f"Backup created: {backup_file}")
except Exception as e:
messagebox.showwarning("Backup Failed", f"Could not create backup: {str(e)}")
# Save fixed file
try:
# Decide where to save the fixed content
save_path = self.file_path
with open(save_path, "w", encoding="utf-8") as file:
file.write(fixed_content)
messagebox.showinfo("Success", f"JSON fixed successfully!\nSaved to: {save_path}")
self.status_label.config(text="JSON fixed successfully!")
# Refresh to show the fixed file is now valid
self.content = fixed_content
self.errors = self.check_json_syntax(self.content)
self.display_errors()
except Exception as e:
messagebox.showerror("Error", f"Failed to save fixed file: {str(e)}")
def fix_json(self, content):
# First, detect if the JSON is supposed to be an object or array
is_object = False
is_array = False
# Get the first non-whitespace character to determine structure
stripped = content.strip()
if stripped:
first_char = stripped[0]
is_object = first_char == '{'
is_array = first_char == '['
try:
# Step 1: Remove trailing commas
content = re.sub(r",\s*([\]}])", r"\1", content)
# Step 2: Fix missing quotes around property names
content = re.sub(r'([{,]\s*)([a-zA-Z0-9_$]+)(\s*:)', r'\1"\2"\3', content)
# Step 3: Replace single quotes with double quotes (but handle escaped quotes properly)
content = re.sub(r"(?<![\\])(')(.*?)(?<![\\])(')", r'"\2"', content)
# Step 4: Handle escaped single quotes
content = content.replace("\\'", "'")
# Step 5: Fix unescaped control characters
for char in ['\n', '\r', '\t']:
content = content.replace(char, f"\\{char}")
# Step 6: Ensure unescaped quotes inside strings are escaped
# This is complex, so we'll do a simplified version
def escape_quotes_in_strings(match):
prefix = match.group(1)
string_content = match.group(2)
# Escape unescaped double quotes
fixed_content = re.sub(r'(?<![\\])"', r'\\"', string_content)
return f'{prefix}"{fixed_content}"'
# This pattern might need more refinement for complex cases
content = re.sub(r'([:,\[\{]\s*)"(.*?)(?<![\\])"', escape_quotes_in_strings, content)
# Step 7: Fix missing colons
content = re.sub(r'("[\w\s]+")(\s+)("[\w\s]+"|\d+|\{|\[|true|false|null)', r'\1: \3', content)
# Try to parse the fixed content
try:
parsed_data = json.loads(content)
# If we successfully parsed, reformat with proper indentation
if self.preserve_structure_var.get() and (is_object or is_array):
# Preserve the original structure (object or array)
return json.dumps(parsed_data, indent=2)
else:
# Use the parsed structure directly
return json.dumps(parsed_data, indent=2)
except json.JSONDecodeError:
# If first fix attempt failed, try more aggressive measures
# Alternative approach: try to wrap with proper structure
if not (stripped.startswith('{') or stripped.startswith('[')):
# If not starting with { or [, try to detect if it's an object without braces
if ':' in stripped and not stripped.startswith('"'):
# Likely an object with missing braces
wrapped_content = '{' + stripped + '}'
else:
# Try as array
wrapped_content = '[' + stripped + ']'
try:
parsed_data = json.loads(wrapped_content)
return json.dumps(parsed_data, indent=2)
except:
pass
# If all else fails, notify user we couldn't fully fix it
messagebox.showwarning("Warning",
"Advanced fixes applied but JSON may still have issues. Manual editing might be required.")
return content
except Exception as e:
messagebox.showerror("Error", f"Failed to fix JSON: {str(e)}")
return content
if __name__ == "__main__":
root = tk.Tk()
app = SyntaxCorrector(root)
root.mainloop()