py-json-analyzer 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- json_explorer/__init__.py +1 -0
- json_explorer/__main__.py +4 -0
- json_explorer/analyzer.py +196 -0
- json_explorer/cli.py +137 -0
- json_explorer/filter_parser.py +132 -0
- json_explorer/interactive.py +490 -0
- json_explorer/main.py +163 -0
- json_explorer/search.py +423 -0
- json_explorer/stats.py +337 -0
- json_explorer/tree_view.py +161 -0
- json_explorer/utils.py +114 -0
- json_explorer/visualizer.py +1053 -0
- py_json_analyzer-0.1.0.dist-info/METADATA +275 -0
- py_json_analyzer-0.1.0.dist-info/RECORD +25 -0
- py_json_analyzer-0.1.0.dist-info/WHEEL +5 -0
- py_json_analyzer-0.1.0.dist-info/entry_points.txt +2 -0
- py_json_analyzer-0.1.0.dist-info/licenses/LICENSE +21 -0
- py_json_analyzer-0.1.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/test_analyzer.py +85 -0
- tests/test_search.py +192 -0
- tests/test_stats.py +197 -0
- tests/test_tree_view.py +188 -0
- tests/test_utils.py +109 -0
- tests/test_visualizer.py +96 -0
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from rich.prompt import Prompt, Confirm
|
|
6
|
+
from rich.panel import Panel
|
|
7
|
+
from rich.table import Table
|
|
8
|
+
|
|
9
|
+
from .tree_view import print_json_analysis, print_compact_tree
|
|
10
|
+
from .search import JsonSearcher, SearchMode
|
|
11
|
+
from .stats import DataStatsAnalyzer
|
|
12
|
+
from .visualizer import JSONVisualizer
|
|
13
|
+
from .filter_parser import FilterExpressionParser
|
|
14
|
+
from .utils import load_json
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class InteractiveHandler:
|
|
18
|
+
"""Handle interactive mode operations."""
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
self.data = None
|
|
22
|
+
self.source = None
|
|
23
|
+
self.console = Console()
|
|
24
|
+
self.searcher = JsonSearcher()
|
|
25
|
+
self.analyzer = DataStatsAnalyzer()
|
|
26
|
+
self.visualizer = JSONVisualizer()
|
|
27
|
+
|
|
28
|
+
def set_data(self, data, source):
|
|
29
|
+
"""Set the data and source for processing."""
|
|
30
|
+
self.data = data
|
|
31
|
+
self.source = source
|
|
32
|
+
|
|
33
|
+
def run(self):
|
|
34
|
+
"""Run interactive mode."""
|
|
35
|
+
if not self.data:
|
|
36
|
+
self.console.print("ā [red]No data loaded. Please load data first.[/red]")
|
|
37
|
+
return 1
|
|
38
|
+
|
|
39
|
+
self.console.print(f"\nšÆ [bold green]Interactive JSON Explorer[/bold green]")
|
|
40
|
+
self.console.print(f"š [cyan]Loaded: {self.source}[/cyan]\n")
|
|
41
|
+
|
|
42
|
+
while True:
|
|
43
|
+
self._show_main_menu()
|
|
44
|
+
choice = Prompt.ask(
|
|
45
|
+
"\n[bold]Choose an option[/bold]",
|
|
46
|
+
choices=["1", "2", "3", "4", "5", "6", "7", "8", "9", "q"],
|
|
47
|
+
default="q",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
if choice == "q":
|
|
51
|
+
self.console.print("š [yellow]Goodbye![/yellow]")
|
|
52
|
+
break
|
|
53
|
+
elif choice == "1":
|
|
54
|
+
self._interactive_tree_view()
|
|
55
|
+
elif choice == "2":
|
|
56
|
+
self._interactive_search()
|
|
57
|
+
elif choice == "3":
|
|
58
|
+
self._interactive_stats()
|
|
59
|
+
elif choice == "4":
|
|
60
|
+
self._interactive_visualization()
|
|
61
|
+
elif choice == "5":
|
|
62
|
+
self._interactive_filter_search()
|
|
63
|
+
elif choice == "6":
|
|
64
|
+
self._interactive_advanced_search()
|
|
65
|
+
elif choice == "7":
|
|
66
|
+
self._show_filter_help()
|
|
67
|
+
elif choice == "8":
|
|
68
|
+
self._load_new_data()
|
|
69
|
+
elif choice == "9":
|
|
70
|
+
self._show_data_summary()
|
|
71
|
+
|
|
72
|
+
return 0
|
|
73
|
+
|
|
74
|
+
def _show_main_menu(self):
|
|
75
|
+
"""Display the main menu."""
|
|
76
|
+
menu_panel = Panel.fit(
|
|
77
|
+
"""[bold blue]š Main Menu[/bold blue]
|
|
78
|
+
|
|
79
|
+
[cyan]1.[/cyan] š³ Tree View (Structure Analysis)
|
|
80
|
+
[cyan]2.[/cyan] š Search (Keys/Values)
|
|
81
|
+
[cyan]3.[/cyan] š Statistics & Analysis
|
|
82
|
+
[cyan]4.[/cyan] š Visualizations
|
|
83
|
+
[cyan]5.[/cyan] šÆ Advanced Filter Search
|
|
84
|
+
[cyan]6.[/cyan] š Advanced Search Options
|
|
85
|
+
[cyan]7.[/cyan] ā Filter Expression Help
|
|
86
|
+
[cyan]8.[/cyan] š Load New Data
|
|
87
|
+
[cyan]9.[/cyan] š Data Summary
|
|
88
|
+
[cyan]q.[/cyan] šŖ Quit""",
|
|
89
|
+
border_style="blue",
|
|
90
|
+
)
|
|
91
|
+
self.console.print(menu_panel)
|
|
92
|
+
|
|
93
|
+
def _interactive_tree_view(self):
|
|
94
|
+
"""Interactive tree view options."""
|
|
95
|
+
self.console.print("\nš³ [bold]Tree View Options[/bold]")
|
|
96
|
+
|
|
97
|
+
tree_type = Prompt.ask(
|
|
98
|
+
"Select tree view type",
|
|
99
|
+
choices=["compact", "analysis", "raw"],
|
|
100
|
+
default="compact",
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
if tree_type == "raw":
|
|
104
|
+
print_json_analysis(self.data, self.source, show_raw=True)
|
|
105
|
+
elif tree_type == "analysis":
|
|
106
|
+
print_json_analysis(self.data, self.source)
|
|
107
|
+
elif tree_type == "compact":
|
|
108
|
+
print_compact_tree(self.data, self.source)
|
|
109
|
+
|
|
110
|
+
def _interactive_search(self):
|
|
111
|
+
"""Interactive search functionality."""
|
|
112
|
+
self.console.print("\nš [bold]Search Options[/bold]")
|
|
113
|
+
|
|
114
|
+
search_type = Prompt.ask(
|
|
115
|
+
"What would you like to search for?",
|
|
116
|
+
choices=["key", "value", "pair"],
|
|
117
|
+
default="key",
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
search_term = Prompt.ask("Enter search term")
|
|
121
|
+
|
|
122
|
+
mode_str = Prompt.ask(
|
|
123
|
+
"Search mode",
|
|
124
|
+
choices=[
|
|
125
|
+
"exact",
|
|
126
|
+
"contains",
|
|
127
|
+
"regex",
|
|
128
|
+
"startswith",
|
|
129
|
+
"endswith",
|
|
130
|
+
"case_insensitive",
|
|
131
|
+
],
|
|
132
|
+
default="exact",
|
|
133
|
+
)
|
|
134
|
+
search_mode = SearchMode(mode_str)
|
|
135
|
+
|
|
136
|
+
if search_type == "key":
|
|
137
|
+
self.console.print(f"\nš Searching for key: '{search_term}'")
|
|
138
|
+
results = self.searcher.search_keys(self.data, search_term, search_mode)
|
|
139
|
+
self.searcher.print_results(results, mode=search_mode)
|
|
140
|
+
|
|
141
|
+
elif search_type == "value":
|
|
142
|
+
limit_types = Confirm.ask("Limit search to specific data types?")
|
|
143
|
+
value_types = None
|
|
144
|
+
|
|
145
|
+
if limit_types:
|
|
146
|
+
type_choices = ["str", "int", "float", "bool", "list", "dict"]
|
|
147
|
+
selected_types = []
|
|
148
|
+
for type_name in type_choices:
|
|
149
|
+
if Confirm.ask(f"Include {type_name} values?"):
|
|
150
|
+
selected_types.append(eval(type_name))
|
|
151
|
+
value_types = set(selected_types) if selected_types else None
|
|
152
|
+
|
|
153
|
+
self.console.print(f"\nš Searching for value: '{search_term}'")
|
|
154
|
+
results = self.searcher.search_values(
|
|
155
|
+
self.data, search_term, search_mode, value_types=value_types
|
|
156
|
+
)
|
|
157
|
+
self.searcher.print_results(results, mode=search_mode)
|
|
158
|
+
|
|
159
|
+
elif search_type == "pair":
|
|
160
|
+
value_term = Prompt.ask("Enter value to search for")
|
|
161
|
+
self.console.print(
|
|
162
|
+
f"\nš Searching for key-value pair: '{search_term}' = '{value_term}'"
|
|
163
|
+
)
|
|
164
|
+
results = self.searcher.search_key_value_pairs(
|
|
165
|
+
self.data, search_term, value_term, search_mode, search_mode
|
|
166
|
+
)
|
|
167
|
+
self.searcher.print_results(results, mode=search_mode)
|
|
168
|
+
|
|
169
|
+
def _interactive_stats(self):
|
|
170
|
+
"""Interactive statistics display."""
|
|
171
|
+
self.console.print("\nš [bold]Statistics & Analysis[/bold]")
|
|
172
|
+
|
|
173
|
+
detailed = Confirm.ask("Show detailed statistics?", default=False)
|
|
174
|
+
self.analyzer.print_summary(self.data, detailed=detailed)
|
|
175
|
+
|
|
176
|
+
def _interactive_visualization(self):
|
|
177
|
+
"""Interactive visualization options."""
|
|
178
|
+
self.console.print("\nš [bold]Visualization Options[/bold]")
|
|
179
|
+
|
|
180
|
+
viz_format = Prompt.ask(
|
|
181
|
+
"Select visualization format",
|
|
182
|
+
choices=["terminal", "matplotlib", "browser", "all"],
|
|
183
|
+
default="matplotlib",
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
detailed = Confirm.ask("Generate detailed visualizations?", default=False)
|
|
187
|
+
save_path = None
|
|
188
|
+
|
|
189
|
+
if Confirm.ask("Save visualizations to file?"):
|
|
190
|
+
save_path = Prompt.ask("Enter save path (optional)", default="")
|
|
191
|
+
save_path = save_path if save_path else None
|
|
192
|
+
|
|
193
|
+
open_browser = True
|
|
194
|
+
if viz_format in ["browser", "all"]:
|
|
195
|
+
open_browser = Confirm.ask(
|
|
196
|
+
"Open browser for HTML visualizations?", default=True
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
self.visualizer.visualize(
|
|
201
|
+
self.data,
|
|
202
|
+
output=viz_format,
|
|
203
|
+
save_path=save_path,
|
|
204
|
+
detailed=detailed,
|
|
205
|
+
open_browser=open_browser,
|
|
206
|
+
)
|
|
207
|
+
except Exception as e:
|
|
208
|
+
self.console.print(f"ā [red]Visualization error: {e}[/red]")
|
|
209
|
+
|
|
210
|
+
def _interactive_filter_search(self):
|
|
211
|
+
"""Interactive filter search with expression builder."""
|
|
212
|
+
self.console.print("\nšÆ [bold]Advanced Filter Search[/bold]")
|
|
213
|
+
|
|
214
|
+
self._show_filter_examples()
|
|
215
|
+
|
|
216
|
+
filter_expr = Prompt.ask(
|
|
217
|
+
"\n[bold]Enter filter expression[/bold]",
|
|
218
|
+
default="isinstance(value, (int, float)) and value > 0",
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
try:
|
|
222
|
+
filter_func = FilterExpressionParser.parse_filter(filter_expr)
|
|
223
|
+
|
|
224
|
+
self.console.print(f"\nšÆ [yellow]Applying filter: {filter_expr}[/yellow]")
|
|
225
|
+
|
|
226
|
+
results = self.searcher.search_with_filter(self.data, filter_func)
|
|
227
|
+
|
|
228
|
+
if results:
|
|
229
|
+
show_tree = Confirm.ask("Display results as tree?", default=False)
|
|
230
|
+
self.searcher.print_results(results, show_tree=show_tree)
|
|
231
|
+
|
|
232
|
+
if Confirm.ask("Save results to file?"):
|
|
233
|
+
self._save_search_results(results, filter_expr)
|
|
234
|
+
else:
|
|
235
|
+
self.console.print("[yellow]No results found.[/yellow]")
|
|
236
|
+
|
|
237
|
+
except Exception as e:
|
|
238
|
+
self.console.print(f"ā [red]Filter error: {e}[/red]")
|
|
239
|
+
self.console.print(
|
|
240
|
+
"[yellow]Please check your filter expression syntax.[/yellow]"
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
def _interactive_advanced_search(self):
|
|
244
|
+
"""Advanced search with multiple criteria."""
|
|
245
|
+
self.console.print("\nš [bold]Advanced Search Options[/bold]")
|
|
246
|
+
|
|
247
|
+
search_type = Prompt.ask(
|
|
248
|
+
"Select search type",
|
|
249
|
+
choices=["key", "value", "pair", "filter"],
|
|
250
|
+
default="key",
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
if search_type == "filter":
|
|
254
|
+
return self._interactive_filter_search()
|
|
255
|
+
|
|
256
|
+
search_term = Prompt.ask("Enter search term")
|
|
257
|
+
mode_str = Prompt.ask(
|
|
258
|
+
"Search mode",
|
|
259
|
+
choices=[
|
|
260
|
+
"exact",
|
|
261
|
+
"contains",
|
|
262
|
+
"regex",
|
|
263
|
+
"startswith",
|
|
264
|
+
"endswith",
|
|
265
|
+
"case_insensitive",
|
|
266
|
+
],
|
|
267
|
+
default="exact",
|
|
268
|
+
)
|
|
269
|
+
search_mode = SearchMode(mode_str)
|
|
270
|
+
|
|
271
|
+
self.console.print("\n[bold]Advanced Options:[/bold]")
|
|
272
|
+
|
|
273
|
+
max_results = None
|
|
274
|
+
if Confirm.ask("Limit number of results?"):
|
|
275
|
+
max_results = int(Prompt.ask("Maximum results", default="10"))
|
|
276
|
+
|
|
277
|
+
min_depth = 0
|
|
278
|
+
max_depth = None
|
|
279
|
+
if Confirm.ask("Set depth limits?"):
|
|
280
|
+
min_depth = int(Prompt.ask("Minimum depth", default="0"))
|
|
281
|
+
if Confirm.ask("Set maximum depth?"):
|
|
282
|
+
max_depth = int(Prompt.ask("Maximum depth", default="5"))
|
|
283
|
+
|
|
284
|
+
try:
|
|
285
|
+
if search_type == "key":
|
|
286
|
+
results = self.searcher.search_keys(
|
|
287
|
+
self.data,
|
|
288
|
+
search_term,
|
|
289
|
+
search_mode,
|
|
290
|
+
max_results=max_results,
|
|
291
|
+
min_depth=min_depth,
|
|
292
|
+
max_depth=max_depth,
|
|
293
|
+
)
|
|
294
|
+
elif search_type == "value":
|
|
295
|
+
value_types = (
|
|
296
|
+
self._get_value_types()
|
|
297
|
+
if Confirm.ask("Limit to specific data types?")
|
|
298
|
+
else None
|
|
299
|
+
)
|
|
300
|
+
results = self.searcher.search_values(
|
|
301
|
+
self.data,
|
|
302
|
+
search_term,
|
|
303
|
+
search_mode,
|
|
304
|
+
value_types=value_types,
|
|
305
|
+
max_results=max_results,
|
|
306
|
+
min_depth=min_depth,
|
|
307
|
+
max_depth=max_depth,
|
|
308
|
+
)
|
|
309
|
+
elif search_type == "pair":
|
|
310
|
+
value_term = Prompt.ask("Enter value to search for")
|
|
311
|
+
results = self.searcher.search_key_value_pairs(
|
|
312
|
+
self.data, search_term, value_term, search_mode, search_mode
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
# Display results
|
|
316
|
+
if results:
|
|
317
|
+
show_tree = Confirm.ask("Display results as tree?", default=False)
|
|
318
|
+
self.searcher.print_results(
|
|
319
|
+
results, show_tree=show_tree, mode=search_mode
|
|
320
|
+
)
|
|
321
|
+
else:
|
|
322
|
+
self.console.print("[yellow]No results found.[/yellow]")
|
|
323
|
+
|
|
324
|
+
except Exception as e:
|
|
325
|
+
self.console.print(f"ā [red]Search error: {e}[/red]")
|
|
326
|
+
|
|
327
|
+
def _get_value_types(self):
|
|
328
|
+
"""Get value types selection from user."""
|
|
329
|
+
type_map = {
|
|
330
|
+
"string": str,
|
|
331
|
+
"integer": int,
|
|
332
|
+
"float": float,
|
|
333
|
+
"boolean": bool,
|
|
334
|
+
"list": list,
|
|
335
|
+
"dict": dict,
|
|
336
|
+
}
|
|
337
|
+
selected_types = []
|
|
338
|
+
for type_name, type_class in type_map.items():
|
|
339
|
+
if Confirm.ask(f"Include {type_name}?"):
|
|
340
|
+
selected_types.append(type_class)
|
|
341
|
+
return set(selected_types) if selected_types else None
|
|
342
|
+
|
|
343
|
+
def _show_filter_examples(self):
|
|
344
|
+
"""Show filter expression examples."""
|
|
345
|
+
examples_panel = Panel.fit(
|
|
346
|
+
"""[bold]Example Filter Expressions:[/bold]
|
|
347
|
+
|
|
348
|
+
[green]⢠isinstance(value, int) and value > 10[/green]
|
|
349
|
+
Find integer values greater than 10
|
|
350
|
+
|
|
351
|
+
[green]⢠key.startswith('user') and depth <= 2[/green]
|
|
352
|
+
Find keys starting with 'user' at depth 2 or less
|
|
353
|
+
|
|
354
|
+
[green]⢠'email' in str(value).lower()[/green]
|
|
355
|
+
Find values containing 'email' (case-insensitive)
|
|
356
|
+
|
|
357
|
+
[green]⢠len(str(value)) > 50[/green]
|
|
358
|
+
Find values with string length > 50
|
|
359
|
+
|
|
360
|
+
[green]⢠isinstance(value, list) and len(value) > 5[/green]
|
|
361
|
+
Find lists with more than 5 items
|
|
362
|
+
|
|
363
|
+
[green]⢠depth == 3 and isinstance(value, dict)[/green]
|
|
364
|
+
Find dictionaries at exactly depth 3""",
|
|
365
|
+
title="š” Examples",
|
|
366
|
+
border_style="green",
|
|
367
|
+
)
|
|
368
|
+
self.console.print(examples_panel)
|
|
369
|
+
|
|
370
|
+
def _show_filter_help(self):
|
|
371
|
+
"""Show comprehensive help for filter expressions."""
|
|
372
|
+
help_panel = Panel.fit(
|
|
373
|
+
"""[bold blue]š§ Filter Expression Reference[/bold blue]
|
|
374
|
+
|
|
375
|
+
[bold]Available Variables:[/bold]
|
|
376
|
+
⢠[cyan]key[/cyan] - The current key name (string)
|
|
377
|
+
⢠[cyan]value[/cyan] - The current value (any type)
|
|
378
|
+
⢠[cyan]depth[/cyan] - Current depth in the JSON structure (integer)
|
|
379
|
+
|
|
380
|
+
[bold]Available Functions:[/bold]
|
|
381
|
+
⢠[cyan]isinstance(value, type)[/cyan] - Check value type
|
|
382
|
+
⢠[cyan]len(value)[/cyan] - Get length of value
|
|
383
|
+
⢠[cyan]str(value)[/cyan] - Convert to string
|
|
384
|
+
⢠[cyan]int(value)[/cyan], [cyan]float(value)[/cyan] - Type conversion
|
|
385
|
+
⢠[cyan]hasattr(value, attr)[/cyan] - Check if value has attribute
|
|
386
|
+
⢠[cyan]type(value)[/cyan] - Get value type
|
|
387
|
+
|
|
388
|
+
[bold]String Methods (use with str(value) or key):[/bold]
|
|
389
|
+
⢠[cyan].startswith('prefix')[/cyan]
|
|
390
|
+
⢠[cyan].endswith('suffix')[/cyan]
|
|
391
|
+
⢠[cyan].lower()[/cyan], [cyan].upper()[/cyan]
|
|
392
|
+
⢠[cyan]'substring' in string[/cyan]
|
|
393
|
+
|
|
394
|
+
[bold]Operators:[/bold]
|
|
395
|
+
⢠Comparison: [cyan]==, !=, <, <=, >, >=[/cyan]
|
|
396
|
+
⢠Logical: [cyan]and, or, not[/cyan]
|
|
397
|
+
⢠Membership: [cyan]in, not in[/cyan]
|
|
398
|
+
⢠Arithmetic: [cyan]+, -, *, /, %[/cyan]
|
|
399
|
+
|
|
400
|
+
[bold]Complex Examples:[/bold]
|
|
401
|
+
[green]isinstance(value, dict) and len(value) > 3[/green]
|
|
402
|
+
[green]key.endswith('_id') and isinstance(value, int)[/green]
|
|
403
|
+
[green]depth >= 2 and 'user' in key.lower()[/green]
|
|
404
|
+
[green]isinstance(value, str) and len(value) > 10 and '@' in value[/green]""",
|
|
405
|
+
border_style="blue",
|
|
406
|
+
)
|
|
407
|
+
self.console.print(help_panel)
|
|
408
|
+
|
|
409
|
+
def _load_new_data(self):
|
|
410
|
+
"""Load new JSON data."""
|
|
411
|
+
self.console.print("\nš [bold]Load New Data[/bold]")
|
|
412
|
+
|
|
413
|
+
source_type = Prompt.ask("Data source", choices=["file", "url"], default="file")
|
|
414
|
+
|
|
415
|
+
try:
|
|
416
|
+
if source_type == "file":
|
|
417
|
+
file_path = Prompt.ask("Enter file path")
|
|
418
|
+
self.source, self.data = load_json(file_path, None)
|
|
419
|
+
else:
|
|
420
|
+
url = Prompt.ask("Enter URL")
|
|
421
|
+
self.source, self.data = load_json(None, url)
|
|
422
|
+
|
|
423
|
+
self.console.print(f"ā
[green]Successfully loaded: {self.source}[/green]")
|
|
424
|
+
|
|
425
|
+
except Exception as e:
|
|
426
|
+
self.console.print(f"ā [red]Failed to load data: {e}[/red]")
|
|
427
|
+
|
|
428
|
+
def _show_data_summary(self):
|
|
429
|
+
"""Show a quick summary of the loaded data."""
|
|
430
|
+
if not self.data:
|
|
431
|
+
self.console.print("ā [red]No data loaded[/red]")
|
|
432
|
+
return
|
|
433
|
+
|
|
434
|
+
self.console.print("\nš [bold]Data Summary[/bold]")
|
|
435
|
+
|
|
436
|
+
summary_table = Table(title="Quick Overview")
|
|
437
|
+
summary_table.add_column("Property", style="cyan")
|
|
438
|
+
summary_table.add_column("Value", style="green")
|
|
439
|
+
|
|
440
|
+
data_type = type(self.data).__name__
|
|
441
|
+
summary_table.add_row("Data Type", data_type)
|
|
442
|
+
summary_table.add_row("Source", str(self.source))
|
|
443
|
+
|
|
444
|
+
if isinstance(self.data, (dict, list)):
|
|
445
|
+
summary_table.add_row("Length", str(len(self.data)))
|
|
446
|
+
|
|
447
|
+
if isinstance(self.data, dict):
|
|
448
|
+
summary_table.add_row("Top-level Keys", str(len(self.data.keys())))
|
|
449
|
+
if self.data:
|
|
450
|
+
top_keys = list(self.data.keys())[:5]
|
|
451
|
+
summary_table.add_row(
|
|
452
|
+
"Sample Keys", ", ".join(str(k) for k in top_keys)
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
self.console.print(summary_table)
|
|
456
|
+
|
|
457
|
+
def _save_search_results(self, results, filter_expr):
|
|
458
|
+
"""Save search results to a file."""
|
|
459
|
+
filename = Prompt.ask(
|
|
460
|
+
"Enter filename",
|
|
461
|
+
default=f"search_results_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
try:
|
|
465
|
+
serializable_results = []
|
|
466
|
+
for result in results:
|
|
467
|
+
serializable_results.append(
|
|
468
|
+
{
|
|
469
|
+
"path": result.path,
|
|
470
|
+
"value": result.value,
|
|
471
|
+
"parent_key": result.parent_key,
|
|
472
|
+
"depth": result.depth,
|
|
473
|
+
"data_type": result.data_type,
|
|
474
|
+
}
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
output_data = {
|
|
478
|
+
"filter_expression": filter_expr,
|
|
479
|
+
"timestamp": datetime.now().isoformat(),
|
|
480
|
+
"total_results": len(results),
|
|
481
|
+
"results": serializable_results,
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
with open(filename, "w", encoding="utf-8") as f:
|
|
485
|
+
json.dump(output_data, f, indent=2, ensure_ascii=False)
|
|
486
|
+
|
|
487
|
+
self.console.print(f"ā
[green]Results saved to: {filename}[/green]")
|
|
488
|
+
|
|
489
|
+
except Exception as e:
|
|
490
|
+
self.console.print(f"ā [red]Error saving results: {e}[/red]")
|
json_explorer/main.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
from .cli import CLIHandler
|
|
7
|
+
from .interactive import InteractiveHandler
|
|
8
|
+
from .utils import load_json
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class JSONExplorer:
|
|
13
|
+
"""Main JSON Explorer application coordinator."""
|
|
14
|
+
|
|
15
|
+
def __init__(self):
|
|
16
|
+
self.data = None
|
|
17
|
+
self.source = None
|
|
18
|
+
self.console = Console()
|
|
19
|
+
self.cli_handler = CLIHandler()
|
|
20
|
+
self.interactive_handler = InteractiveHandler()
|
|
21
|
+
|
|
22
|
+
def load_data(self, file_path=None, url=None):
|
|
23
|
+
"""Load JSON data from file or URL."""
|
|
24
|
+
try:
|
|
25
|
+
self.source, self.data = load_json(file_path, url)
|
|
26
|
+
return True
|
|
27
|
+
except Exception as e:
|
|
28
|
+
self.console.print(f"ā [red]Error loading data: {e}[/red]")
|
|
29
|
+
return False
|
|
30
|
+
|
|
31
|
+
def run(self, args):
|
|
32
|
+
"""Main execution method."""
|
|
33
|
+
if not self.load_data(args.file, args.url):
|
|
34
|
+
return 1
|
|
35
|
+
|
|
36
|
+
self.cli_handler.set_data(self.data, self.source)
|
|
37
|
+
self.interactive_handler.set_data(self.data, self.source)
|
|
38
|
+
|
|
39
|
+
if args.interactive or not self._has_cli_actions(args):
|
|
40
|
+
return self.interactive_handler.run()
|
|
41
|
+
else:
|
|
42
|
+
return self.cli_handler.run(args)
|
|
43
|
+
|
|
44
|
+
def _has_cli_actions(self, args) -> bool:
|
|
45
|
+
"""Check if any CLI-specific actions are requested."""
|
|
46
|
+
return any([args.tree, args.search, args.stats, args.plot])
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def create_parser():
|
|
50
|
+
"""Create and configure the argument parser."""
|
|
51
|
+
parser = argparse.ArgumentParser(
|
|
52
|
+
description="š JSON Explorer - Analyze, visualize, and explore JSON data",
|
|
53
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
54
|
+
epilog="""
|
|
55
|
+
Examples:
|
|
56
|
+
%(prog)s data.json --interactive
|
|
57
|
+
%(prog)s data.json --tree compact --stats
|
|
58
|
+
%(prog)s data.json --search "name" --search-type key
|
|
59
|
+
%(prog)s data.json --search "isinstance(value, int) and value > 10" --search-type filter
|
|
60
|
+
%(prog)s --url https://api.example.com/data --plot --tree-results
|
|
61
|
+
""",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Data source
|
|
65
|
+
parser.add_argument("file", nargs="?", help="Path to JSON file")
|
|
66
|
+
parser.add_argument("--url", type=str, help="URL to fetch JSON from")
|
|
67
|
+
|
|
68
|
+
# Interactive mode
|
|
69
|
+
parser.add_argument(
|
|
70
|
+
"--interactive", "-i", action="store_true", help="Run in interactive mode"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Tree operations
|
|
74
|
+
parser.add_argument(
|
|
75
|
+
"--tree",
|
|
76
|
+
choices=["compact", "analysis", "raw"],
|
|
77
|
+
help="Display JSON tree structure",
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Search operations
|
|
81
|
+
search_group = parser.add_argument_group("search options")
|
|
82
|
+
search_group.add_argument(
|
|
83
|
+
"--search", type=str, help="Search query or filter expression"
|
|
84
|
+
)
|
|
85
|
+
search_group.add_argument(
|
|
86
|
+
"--search-type",
|
|
87
|
+
choices=["key", "value", "pair", "filter"],
|
|
88
|
+
default="key",
|
|
89
|
+
help="Type of search to perform",
|
|
90
|
+
)
|
|
91
|
+
search_group.add_argument(
|
|
92
|
+
"--search-value",
|
|
93
|
+
type=str,
|
|
94
|
+
help="Value to search for (used with --search-type pair)",
|
|
95
|
+
)
|
|
96
|
+
search_group.add_argument(
|
|
97
|
+
"--search-mode",
|
|
98
|
+
type=str,
|
|
99
|
+
choices=[
|
|
100
|
+
"exact",
|
|
101
|
+
"contains",
|
|
102
|
+
"regex",
|
|
103
|
+
"startswith",
|
|
104
|
+
"endswith",
|
|
105
|
+
"case_insensitive",
|
|
106
|
+
],
|
|
107
|
+
default="exact",
|
|
108
|
+
help="Search mode",
|
|
109
|
+
)
|
|
110
|
+
search_group.add_argument(
|
|
111
|
+
"--tree-results",
|
|
112
|
+
action="store_true",
|
|
113
|
+
help="Display search results in tree format",
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Analysis options
|
|
117
|
+
analysis_group = parser.add_argument_group("analysis options")
|
|
118
|
+
analysis_group.add_argument("--stats", action="store_true", help="Show statistics")
|
|
119
|
+
analysis_group.add_argument(
|
|
120
|
+
"--detailed", action="store_true", help="Show detailed analysis/statistics"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Visualization options
|
|
124
|
+
viz_group = parser.add_argument_group("visualization options")
|
|
125
|
+
viz_group.add_argument(
|
|
126
|
+
"--plot", action="store_true", help="Generate visualizations"
|
|
127
|
+
)
|
|
128
|
+
viz_group.add_argument(
|
|
129
|
+
"--plot-format",
|
|
130
|
+
choices=["terminal", "matplotlib", "browser", "all"],
|
|
131
|
+
default="matplotlib",
|
|
132
|
+
help="Visualization format",
|
|
133
|
+
)
|
|
134
|
+
viz_group.add_argument("--save-path", type=str, help="Path to save visualizations")
|
|
135
|
+
viz_group.add_argument(
|
|
136
|
+
"--no-browser",
|
|
137
|
+
action="store_true",
|
|
138
|
+
help="Don't open browser for HTML visualizations",
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
return parser
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def main():
|
|
145
|
+
"""Main entry point for the JSON Explorer."""
|
|
146
|
+
parser = create_parser()
|
|
147
|
+
args = parser.parse_args()
|
|
148
|
+
|
|
149
|
+
if len(sys.argv) == 1:
|
|
150
|
+
parser.print_help()
|
|
151
|
+
return 1
|
|
152
|
+
|
|
153
|
+
if not (args.file or args.url):
|
|
154
|
+
print("ā Error: You must provide a file path or --url")
|
|
155
|
+
parser.print_help()
|
|
156
|
+
return 1
|
|
157
|
+
|
|
158
|
+
explorer = JSONExplorer()
|
|
159
|
+
return explorer.run(args)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
if __name__ == "__main__":
|
|
163
|
+
sys.exit(main())
|