UNPKG

jsonfieldexplorer

Version:

Node.js tool to efficiently explore and list all field paths in a JSON object. Perfect for understanding complex JSON structures, it recursively analyzes JSON data to provide a clear summary of nested fields and arrays.

238 lines (210 loc) 7.3 kB
import readlineSync from "readline-sync"; import { summarizePaths, pathsToLines } from "./jfe.js"; export function startInteractiveMode(jsonObject, options = {}) { console.log("🔍 JSON Field Explorer - Interactive Mode"); console.log("Type 'help' for available commands, 'exit' to quit\n"); // Pre-calculate all paths once const maxDepth = options.maxDepth || Infinity; const allPaths = summarizePaths(jsonObject, "", {}, null, 0, maxDepth); const allLines = pathsToLines(allPaths, options); // Interactive state let currentFilter = ""; let currentSort = "path"; // path, type, alpha let showStats = options.stats || false; let maxDepthCurrent = options.maxDepth || Infinity; const commands = { help: () => { console.log("Available commands:"); console.log(" help - Show this help message"); console.log(" list - Show all fields (current view)"); console.log(" filter <pattern> - Filter fields by path pattern (regex supported)"); console.log(" sort <method> - Sort by: path, type, alpha"); console.log(" stats - Toggle statistics mode"); console.log(" depth <number> - Set max depth (use 'all' for unlimited)"); console.log(" search <term> - Quick search for fields containing term"); console.log(" count - Show count of current results"); console.log(" reset - Reset all filters and settings"); console.log(" exit - Exit interactive mode"); console.log(""); }, list: () => { displayCurrentView(allPaths, { filter: currentFilter, sort: currentSort, stats: showStats, maxDepth: maxDepthCurrent }); }, filter: (pattern) => { if (!pattern) { console.log(`Current filter: ${currentFilter || "(none)"}`); return; } currentFilter = pattern; console.log(`Filter set to: ${pattern}`); displayCurrentView(allPaths, { filter: currentFilter, sort: currentSort, stats: showStats, maxDepth: maxDepthCurrent }); }, sort: (method) => { const validSorts = ["path", "type", "alpha"]; if (!method) { console.log(`Current sort: ${currentSort}`); console.log(`Valid sorts: ${validSorts.join(", ")}`); return; } if (!validSorts.includes(method)) { console.log(`Invalid sort method. Valid sorts: ${validSorts.join(", ")}`); return; } currentSort = method; console.log(`Sort set to: ${method}`); displayCurrentView(allPaths, { filter: currentFilter, sort: currentSort, stats: showStats, maxDepth: maxDepthCurrent }); }, stats: () => { showStats = !showStats; console.log(`Statistics mode: ${showStats ? "ON" : "OFF"}`); displayCurrentView(allPaths, { filter: currentFilter, sort: currentSort, stats: showStats, maxDepth: maxDepthCurrent }); }, depth: (value) => { if (!value) { console.log(`Current max depth: ${maxDepthCurrent === Infinity ? "unlimited" : maxDepthCurrent}`); return; } if (value === "all") { maxDepthCurrent = Infinity; console.log("Max depth set to: unlimited"); } else { const depth = parseInt(value); if (isNaN(depth) || depth < 1) { console.log("Invalid depth. Use a positive number or 'all'."); return; } maxDepthCurrent = depth; console.log(`Max depth set to: ${depth}`); } // Recalculate paths with new depth const newPaths = summarizePaths(jsonObject, "", {}, null, 0, maxDepthCurrent); Object.assign(allPaths, newPaths); displayCurrentView(allPaths, { filter: currentFilter, sort: currentSort, stats: showStats, maxDepth: maxDepthCurrent }); }, search: (term) => { if (!term) { console.log("Please provide a search term"); return; } console.log(`Searching for: ${term}`); currentFilter = term; displayCurrentView(allPaths, { filter: currentFilter, sort: currentSort, stats: showStats, maxDepth: maxDepthCurrent }); }, count: () => { const filtered = filterLines(pathsToLines(allPaths, { stats: showStats }), currentFilter); console.log(`Current view shows ${filtered.length} field(s)`); }, reset: () => { currentFilter = ""; currentSort = "path"; showStats = options.stats || false; maxDepthCurrent = options.maxDepth || Infinity; console.log("All filters and settings reset"); displayCurrentView(allPaths, { filter: currentFilter, sort: currentSort, stats: showStats, maxDepth: maxDepthCurrent }); } }; // Show initial view commands.list(); // Interactive loop while (true) { const input = readlineSync.question("jfe> ").trim(); if (input === "exit" || input === "quit") { console.log("Goodbye! 👋"); break; } if (!input) continue; const [command, ...args] = input.split(/\s+/); const handler = commands[command]; if (handler) { try { handler(args.join(" ")); } catch (error) { console.log(`Error: ${error.message}`); } } else { console.log(`Unknown command: ${command}. Type 'help' for available commands.`); } console.log(""); // Add spacing } } export function filterLines(lines, filter) { if (!filter) return lines; try { const regex = new RegExp(filter, "i"); return lines.filter(line => regex.test(line)); } catch (error) { // If regex fails, fall back to string contains return lines.filter(line => line.toLowerCase().includes(filter.toLowerCase())); } } export function sortLines(lines, sortMethod) { switch (sortMethod) { case "alpha": return [...lines].sort((a, b) => a.localeCompare(b)); case "type": return [...lines].sort((a, b) => { const typeA = extractType(a); const typeB = extractType(b); return typeA.localeCompare(typeB) || a.localeCompare(b); }); case "path": default: // Sort by path depth first, then alphabetically return [...lines].sort((a, b) => { const depthA = (a.split(".").length - 1) + (a.includes("[]") ? 0.5 : 0); const depthB = (b.split(".").length - 1) + (b.includes("[]") ? 0.5 : 0); return depthA - depthB || a.localeCompare(b); }); } } export function extractType(line) { const match = line.match(/: (\w+)/); return match ? match[1] : "unknown"; } function displayCurrentView(paths, options) { const lines = pathsToLines(paths, { stats: options.stats }); let filtered = filterLines(lines, options.filter); // Apply sorting filtered = sortLines(filtered, options.sort); if (filtered.length === 0) { console.log("No fields match the current filter."); return; } console.log(`Showing ${filtered.length} field(s):`); filtered.forEach(line => console.log(line)); }