// Name: JSON Key Extractor // Description: Select nested keys from clipboard JSON and copy filtered JSON or Markdown to clipboard. // Author: tayiorbeii // GitHub: tayiorbeii import "@johnlindquist/kit" const readJsonFromClipboard = async (): Promise<any> => { let raw = (await clipboard.readText())?.trim() let parsed: any try { parsed = JSON.parse(raw) return parsed } catch { // Prompt user to paste JSON if clipboard is invalid raw = await editor({ value: raw || "", language: "json", placeholder: "Paste JSON here", footer: "Hit cmd+s to continue...", }) try { parsed = JSON.parse(raw) return parsed } catch { await div(md(`❌ Invalid JSON. Exiting.`)) exit() } } } const isObject = (v: any) => v !== null && typeof v === "object" && !Array.isArray(v) const isArray = Array.isArray // Collect both node paths (objects/arrays) and leaf paths const collectPaths = (node: any, base = ""): string[] => { const paths: string[] = [] const pushIfNotEmpty = (p: string) => { if (p) paths.push(p) } if (isArray(node)) { pushIfNotEmpty(base) for (let i = 0; i < node.length; i++) { const next = base ? `${base}[${i}]` : `[${i}]` paths.push(...collectPaths(node[i], next)) } return paths } if (isObject(node)) { pushIfNotEmpty(base) for (const key of Object.keys(node)) { const next = base ? `${base}.${key}` : key paths.push(...collectPaths(node[key], next)) } return paths } // Primitive pushIfNotEmpty(base) return paths } // Parse "a.b[0].c" -> ["a","b",0,"c"] const parsePath = (pathStr: string): (string | number)[] => { const segs: (string | number)[] = [] const re = /([^[.\]]+)|\[(\d+)\]/g let m: RegExpExecArray | null while ((m = re.exec(pathStr))) { if (m[1] !== undefined) segs.push(m[1]) else if (m[2] !== undefined) segs.push(Number(m[2])) } return segs } const getByPath = (obj: any, pathStr: string): any => { const segs = parsePath(pathStr) let cur = obj for (const seg of segs) { if (cur == null) return undefined cur = cur[seg as any] } return cur } const setByPath = (targetRoot: any, pathStr: string, value: any) => { const segs = parsePath(pathStr) if (segs.length === 0) return let cur = targetRoot for (let i = 0; i < segs.length; i++) { const seg = segs[i] const last = i === segs.length - 1 if (last) { cur[seg as any] = value return } const next = segs[i + 1] const expectedContainer = typeof next === "number" ? [] : {} if (cur[seg as any] == null) { cur[seg as any] = expectedContainer } else { // If type mismatch (rare), coerce const isNextArray = Array.isArray(expectedContainer) if (isNextArray && !Array.isArray(cur[seg as any])) cur[seg as any] = [] if (!isNextArray && (Array.isArray(cur[seg as any]) || typeof cur[seg as any] !== "object")) { cur[seg as any] = {} } } cur = cur[seg as any] } } const toMarkdown = (pairs: { path: string; value: any }[]) => { const sections = pairs.map(({ path, value }) => { const v = typeof value === "string" ? value : value === null || value === undefined ? String(value) : JSON.stringify(value, null, 2) return `## ${path} ${v} ` }) return sections.join("\n") } const json = await readJsonFromClipboard() // Build flattened key list const allPaths = Array.from(new Set(collectPaths(json))).sort((a, b) => a.localeCompare(b)) if (allPaths.length === 0) { await div(md(`No keys found in provided JSON.`)) exit() } // Multi-select keys const selectedPaths: string[] = await select( { placeholder: "Select keys to extract (multi-select)", enter: "Next", hint: "Type to filter. Select both leaf and parent keys as needed.", }, allPaths ) if (!selectedPaths || selectedPaths.length === 0)