# Copyright (c) 2025 Patrick Motsch # All rights reserved. """ JSON code renderer for code generation. """ from .codeRendererBaseTemplate import BaseCodeRenderer from modules.datamodels.datamodelDocument import RenderedDocument from typing import Dict, Any, List, Optional import json class RendererCodeJson(BaseCodeRenderer): """Renders JSON code files.""" @classmethod def getSupportedFormats(cls) -> List[str]: """Return supported JSON formats.""" return ['json'] @classmethod def getFormatAliases(cls) -> List[str]: """Return format aliases.""" return [] @classmethod def getPriority(cls) -> int: """Return priority for JSON code renderer.""" return 85 # Higher than document renderer (80) for code generation @classmethod def getOutputStyle(cls, formatName: Optional[str] = None) -> str: """Return output style classification: JSON is structured data format.""" return 'code' async def renderCodeFiles( self, codeFiles: List[Dict[str, Any]], metadata: Dict[str, Any], userPrompt: str = None ) -> List[RenderedDocument]: """ Render JSON code files. For single file: output as-is For multiple files: output separately (each file is independent JSON) """ renderedDocs = [] for codeFile in codeFiles: if not self._validateCodeFile(codeFile): self.logger.warning(f"Invalid code file: {codeFile.get('filename', 'unknown')}") continue filename = codeFile['filename'] content = codeFile['content'] # Validate JSON syntax and extract statistics parsed = None try: parsed = json.loads(content) # Validate JSON except json.JSONDecodeError as e: self.logger.warning(f"Invalid JSON in {filename}: {e}") # Could fix/format JSON here if needed # Format JSON (pretty print) try: if parsed is None: parsed = json.loads(content) formattedContent = json.dumps(parsed, indent=2, ensure_ascii=False) except Exception: formattedContent = content # Use original if formatting fails # Extract JSON statistics for validation jsonStats = self._extractJsonStatistics(parsed) if parsed else {} # Merge file-specific metadata with project metadata fileMetadata = dict(metadata) if metadata else {} fileMetadata.update({ "filename": filename, "fileType": "json", "statistics": jsonStats }) renderedDocs.append( RenderedDocument( documentData=formattedContent.encode('utf-8'), mimeType="application/json", filename=filename, metadata=fileMetadata ) ) return renderedDocs async def render(self, extractedContent: Dict[str, Any], title: str, userPrompt: str = None, aiService=None) -> List[RenderedDocument]: """ Render method for document generation compatibility. Delegates to document renderer if needed, or handles code files directly. """ # Check if this is code generation (has files array) or document generation (has documents array) if "files" in extractedContent: # Code generation path - use renderCodeFiles files = extractedContent.get("files", []) metadata = extractedContent.get("metadata", {}) return await self.renderCodeFiles(files, metadata, userPrompt) else: # Document generation path - delegate to document renderer # Import here to avoid circular dependency from .rendererJson import RendererJson documentRenderer = RendererJson(self.services) return await documentRenderer.render(extractedContent, title, userPrompt, aiService) def _extractJsonStatistics(self, parsed: Any) -> Dict[str, Any]: """Extract JSON statistics for validation (object count, array count, key count).""" try: stats = { "isArray": isinstance(parsed, list), "isObject": isinstance(parsed, dict), "itemCount": 0, "keyCount": 0 } if isinstance(parsed, list): stats["itemCount"] = len(parsed) # Count nested objects/arrays objectCount = sum(1 for item in parsed if isinstance(item, dict)) arrayCount = sum(1 for item in parsed if isinstance(item, list)) stats["objectCount"] = objectCount stats["arrayCount"] = arrayCount elif isinstance(parsed, dict): stats["keyCount"] = len(parsed) stats["keys"] = list(parsed.keys()) # Count nested objects/arrays objectCount = sum(1 for v in parsed.values() if isinstance(v, dict)) arrayCount = sum(1 for v in parsed.values() if isinstance(v, list)) stats["objectCount"] = objectCount stats["arrayCount"] = arrayCount return stats except Exception as e: self.logger.warning(f"JSON statistics extraction failed: {e}") return {}