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.
@@ -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())