Skip to main content
Glama
jsonASTAnalyzer.ts9.45 kB
/** * @fileOverview: AST-based JSON file analyzer * @module: JsonASTAnalyzer * @keyFunctions: * - JsonASTAnalyzer: AST-based JSON analysis using schemas * - analyzeJsonStructure(): Deep JSON structure analysis * - detectConfigType(): Detect configuration file types * - extractKeys(): Extract all keys with nesting information * @context: Provides comprehensive JSON analysis using AST approach */ import { ASTAnalyzer, ASTSymbol, ASTAnalysisResult } from './astAnalyzer'; import { logger } from '../../../utils/logger'; /** * JSON-specific analysis information */ export interface JsonAnalysisInfo { isArray: boolean; isObject: boolean; isPrimitive: boolean; primitiveType?: 'string' | 'number' | 'boolean' | 'null'; depth: number; keyCount: number; arrayLength?: number; configType?: string; // package.json, tsconfig.json, etc. topLevelKeys: string[]; nestedStructure: Record<string, any>; } /** * JSON AST Analyzer */ export class JsonASTAnalyzer extends ASTAnalyzer { constructor() { super('json'); } /** * Parse JSON content into AST structure */ protected parseLanguageSpecific(content: string): any { try { const parsed = JSON.parse(content); return this.buildASTFromJson(parsed, 'document'); } catch (error) { logger.error('Failed to parse JSON', { error: error instanceof Error ? error.message : String(error), }); return null; } } /** * Build AST structure from parsed JSON */ private buildASTFromJson(value: any, nodeType: string, key?: string): any { const node: any = { type: nodeType, text: JSON.stringify(value, null, 2).slice(0, 100), // Truncate for display startLine: 0, // Would need source map for accurate positions endLine: 0, startColumn: 0, endColumn: 0, children: [], fields: {}, }; if (key !== undefined) { node.fields.key = key; } // Handle different JSON types if (value === null) { node.type = 'null'; } else if (typeof value === 'boolean') { node.type = value ? 'true' : 'false'; } else if (typeof value === 'number') { node.type = 'number'; node.fields.value = value; } else if (typeof value === 'string') { node.type = 'string'; node.fields.value = value; node.text = value; } else if (Array.isArray(value)) { node.type = 'array'; node.children = value.map((item, index) => this.buildASTFromJson(item, this.inferNodeType(item), index.toString()) ); } else if (typeof value === 'object') { node.type = 'object'; // Create pair nodes with the value as a child node.children = Object.entries(value).map(([k, v]) => { const pairNode = { type: 'pair', text: `${k}: ${JSON.stringify(v).slice(0, 50)}`, startLine: 0, endLine: 0, startColumn: 0, endColumn: 0, fields: { key: k }, children: [this.buildASTFromJson(v, this.inferNodeType(v))], }; return pairNode; }); } return node; } /** * Infer node type from JSON value */ private inferNodeType(value: any): string { if (value === null) return 'null'; if (typeof value === 'boolean') return value ? 'true' : 'false'; if (typeof value === 'number') return 'number'; if (typeof value === 'string') return 'string'; if (Array.isArray(value)) return 'array'; if (typeof value === 'object') return 'object'; return 'unknown'; } /** * Analyze JSON file with enhanced information */ public async analyzeJsonFile(filePath: string): Promise<JsonAnalysisInfo | null> { const result = await this.analyzeFile(filePath); if (!result) return null; return this.extractJsonInfo(result); } /** * Analyze JSON content with enhanced information */ public async analyzeJsonContent(content: string): Promise<JsonAnalysisInfo | null> { const result = await this.analyzeContent(content); if (!result) return null; return this.extractJsonInfo(result); } /** * Extract JSON-specific information from analysis result */ private extractJsonInfo(result: ASTAnalysisResult): JsonAnalysisInfo { const ast = result.raw; const rootType = ast?.type || 'unknown'; const info: JsonAnalysisInfo = { isArray: rootType === 'array', isObject: rootType === 'object', isPrimitive: ['string', 'number', 'true', 'false', 'null'].includes(rootType), depth: result.structure.depth, keyCount: 0, topLevelKeys: [], nestedStructure: {}, }; // Handle primitives if (info.isPrimitive) { if (rootType === 'true' || rootType === 'false') { info.primitiveType = 'boolean'; } else if (rootType === 'null') { info.primitiveType = 'null'; } else if (rootType === 'number') { info.primitiveType = 'number'; } else if (rootType === 'string') { info.primitiveType = 'string'; } return info; } // Handle arrays if (info.isArray && ast?.children) { info.arrayLength = ast.children.length; return info; } // Handle objects if (info.isObject && ast?.children) { const keys = ast.children .filter((child: any) => child.type === 'pair' && child.fields?.key) .map((child: any) => child.fields.key); info.keyCount = keys.length; info.topLevelKeys = keys; // Build nested structure map info.nestedStructure = this.buildStructureMap(ast); // Detect configuration file type info.configType = this.detectConfigType(keys); } return info; } /** * Build a map of nested structure */ private buildStructureMap(ast: any, prefix: string = ''): Record<string, any> { const structure: Record<string, any> = {}; if (!ast?.children) return structure; for (const child of ast.children) { if (child.type === 'pair' && child.fields?.key) { const key = child.fields.key; const fullKey = prefix ? `${prefix}.${key}` : key; if (child.children && child.children.length > 0) { const valueNode = child.children[0]; structure[fullKey] = { type: valueNode.type, depth: prefix.split('.').length + 1, }; // Recurse for nested objects if (valueNode.type === 'object') { Object.assign(structure, this.buildStructureMap(valueNode, fullKey)); } } } } return structure; } /** * Detect configuration file type based on keys */ private detectConfigType(keys: string[]): string | undefined { const keySet = new Set(keys); // Package.json detection if (keySet.has('name') && keySet.has('version')) { if (keySet.has('dependencies') || keySet.has('devDependencies')) { return 'package.json'; } } // tsconfig.json detection if (keySet.has('compilerOptions')) { return 'tsconfig.json'; } // ESLint config detection if (keySet.has('rules') || keySet.has('extends')) { if (keySet.has('env') || keySet.has('parser')) { return '.eslintrc.json'; } } // Jest config detection if (keySet.has('testMatch') || keySet.has('testEnvironment')) { return 'jest.config.json'; } // Prettier config detection if (keySet.has('printWidth') || keySet.has('tabWidth') || keySet.has('singleQuote')) { return '.prettierrc.json'; } // VS Code settings detection if (keys.some(k => k.startsWith('editor.') || k.startsWith('workbench.'))) { return 'settings.json'; } return undefined; } /** * Extract symbols from JSON AST */ protected extractSymbols(ast: any): ASTSymbol[] { const symbols: ASTSymbol[] = []; const extractFromNode = (node: any, path: string[] = []) => { if (!node) return; if (node.type === 'pair' && node.fields?.key) { const key = node.fields.key; const currentPath = [...path, key]; symbols.push({ name: currentPath.join('.'), type: 'key', line: node.startLine || 0, column: node.startColumn || 0, scope: path.length > 0 ? path.join('.') : 'root', metadata: { depth: currentPath.length, valueType: node.children?.[0]?.type || 'unknown', }, }); // Recurse into children if (node.children) { for (const child of node.children) { extractFromNode(child, currentPath); } } } else if (node.children) { // Continue traversing for (const child of node.children) { extractFromNode(child, path); } } }; extractFromNode(ast); return symbols; } } /** * Convenience function to create and use JSON analyzer */ export async function analyzeJsonFile(filePath: string): Promise<JsonAnalysisInfo | null> { const analyzer = new JsonASTAnalyzer(); await analyzer.initialize(); return analyzer.analyzeJsonFile(filePath); } /** * Convenience function to analyze JSON content */ export async function analyzeJsonContent(content: string): Promise<JsonAnalysisInfo | null> { const analyzer = new JsonASTAnalyzer(); await analyzer.initialize(); return analyzer.analyzeJsonContent(content); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sbarron/AmbianceMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server