package smartconfig import ( "encoding/json" "fmt" "os" "strings" "github.com/tidwall/gjson" "gopkg.in/yaml.v3" ) // JSONResolver reads values from JSON files. // Usage: ${JSON:/path/to/file.json:json.path} type JSONResolver struct{} // Resolve reads a JSON file and extracts the value at the specified path. // Returns the value as a YAML string that can be parsed back into the config. func (r *JSONResolver) Resolve(value string) (string, error) { parts := strings.SplitN(value, ":", 2) if len(parts) != 2 { return "", fmt.Errorf("invalid JSON resolver format, expected FILE:PATH") } filePath := parts[0] jsonPath := parts[1] data, err := os.ReadFile(filePath) if err != nil { return "", fmt.Errorf("failed to read JSON file %s: %w", filePath, err) } // gjson supports JSON5 syntax including comments, trailing commas, etc. // Special case: if path is ".", return the entire JSON if jsonPath == "." { // Parse and convert to YAML for consistency var jsonData interface{} if err := json.Unmarshal(data, &jsonData); err != nil { return "", fmt.Errorf("failed to parse JSON: %w", err) } yamlBytes, err := yaml.Marshal(jsonData) if err != nil { return "", fmt.Errorf("failed to marshal to YAML: %w", err) } return strings.TrimSpace(string(yamlBytes)), nil } result := gjson.GetBytes(data, jsonPath) if !result.Exists() { return "", fmt.Errorf("path %s not found in JSON file", jsonPath) } // For complex types (objects/arrays), we need to parse and convert to YAML if result.IsObject() || result.IsArray() { // Use result.Value() to get the underlying interface{} jsonData := result.Value() // Convert to YAML yamlBytes, err := yaml.Marshal(jsonData) if err != nil { return "", fmt.Errorf("failed to marshal to YAML: %w", err) } return strings.TrimSpace(string(yamlBytes)), nil } // For simple types, just return the string representation return result.String(), nil }