Skip to content

JSON Output Examples

This example demonstrates how to serialize CVSS vectors to JSON format and deserialize them back, including various formatting options and integration patterns.

Overview

CVSS Parser provides comprehensive JSON support for:

  • Serializing parsed vectors to JSON
  • Deserializing JSON back to vector objects
  • Custom JSON formatting
  • Integration with APIs and databases
  • Batch processing with JSON

Basic JSON Serialization

Simple JSON Output

go
package main

import (
    "encoding/json"
    "fmt"
    "log"

    "github.com/scagogogo/cvss-parser/pkg/cvss"
    "github.com/scagogogo/cvss-parser/pkg/parser"
)

func main() {
    // Parse CVSS vector
    vectorStr := "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
    p := parser.NewCvss3xParser(vectorStr)
    vector, err := p.Parse()
    if err != nil {
        log.Fatal(err)
    }

    // Serialize to JSON
    jsonData, err := json.Marshal(vector)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("Compact JSON:")
    fmt.Println(string(jsonData))
}

Pretty-Printed JSON

go
func prettyPrintJSON(vector *cvss.Cvss3x) {
    // Pretty-print with indentation
    jsonData, err := json.MarshalIndent(vector, "", "  ")
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("Pretty JSON:")
    fmt.Println(string(jsonData))
}

JSON with Additional Information

go
func enrichedJSON(vector *cvss.Cvss3x) {
    // Calculate score
    calculator := cvss.NewCalculator(vector)
    score, err := calculator.Calculate()
    if err != nil {
        log.Fatal(err)
    }

    // Create enriched structure
    enriched := struct {
        Vector    *cvss.Cvss3x `json:"vector"`
        Score     float64      `json:"score"`
        Severity  string       `json:"severity"`
        Timestamp string       `json:"timestamp"`
        Metadata  struct {
            HasTemporal      bool `json:"hasTemporal"`
            HasEnvironmental bool `json:"hasEnvironmental"`
            MetricCount      int  `json:"metricCount"`
        } `json:"metadata"`
    }{
        Vector:    vector,
        Score:     score,
        Severity:  calculator.GetSeverityRating(score),
        Timestamp: time.Now().Format(time.RFC3339),
    }

    enriched.Metadata.HasTemporal = vector.HasTemporal()
    enriched.Metadata.HasEnvironmental = vector.HasEnvironmental()
    enriched.Metadata.MetricCount = countMetrics(vector)

    jsonData, err := json.MarshalIndent(enriched, "", "  ")
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("Enriched JSON:")
    fmt.Println(string(jsonData))
}

func countMetrics(vector *cvss.Cvss3x) int {
    count := 8 // Base metrics
    if vector.HasTemporal() {
        count += 3
    }
    if vector.HasEnvironmental() {
        count += 11
    }
    return count
}

JSON Deserialization

Loading from JSON

go
func loadFromJSON(jsonData []byte) (*cvss.Cvss3x, error) {
    var vector cvss.Cvss3x
    err := json.Unmarshal(jsonData, &vector)
    if err != nil {
        return nil, fmt.Errorf("failed to unmarshal JSON: %w", err)
    }

    // Validate loaded vector
    if !vector.IsValid() {
        return nil, fmt.Errorf("loaded vector is invalid")
    }

    return &vector, nil
}

Round-Trip Validation

go
func validateRoundTrip(original *cvss.Cvss3x) error {
    // Serialize to JSON
    jsonData, err := json.Marshal(original)
    if err != nil {
        return fmt.Errorf("serialization failed: %w", err)
    }

    // Deserialize back
    restored, err := loadFromJSON(jsonData)
    if err != nil {
        return fmt.Errorf("deserialization failed: %w", err)
    }

    // Compare vector strings
    if original.String() != restored.String() {
        return fmt.Errorf("round-trip validation failed: %s != %s", 
            original.String(), restored.String())
    }

    fmt.Println("✓ Round-trip validation successful")
    return nil
}

Loading with Error Recovery

go
func safeLoadFromJSON(jsonData []byte) (*cvss.Cvss3x, error) {
    var vector cvss.Cvss3x

    // Set defaults before unmarshaling
    vector.MajorVersion = 3
    vector.MinorVersion = 1

    err := json.Unmarshal(jsonData, &vector)
    if err != nil {
        return nil, fmt.Errorf("JSON unmarshal failed: %w", err)
    }

    // Post-load validation
    if vector.MajorVersion != 3 {
        return nil, fmt.Errorf("unsupported CVSS version: %d.%d", 
            vector.MajorVersion, vector.MinorVersion)
    }

    if vector.Cvss3xBase == nil {
        return nil, fmt.Errorf("missing base metrics")
    }

    return &vector, nil
}

File Operations

Save to File

go
func saveToFile(vector *cvss.Cvss3x, filename string) error {
    jsonData, err := json.MarshalIndent(vector, "", "  ")
    if err != nil {
        return fmt.Errorf("JSON marshal failed: %w", err)
    }

    err = ioutil.WriteFile(filename, jsonData, 0644)
    if err != nil {
        return fmt.Errorf("file write failed: %w", err)
    }

    fmt.Printf("Vector saved to %s\n", filename)
    return nil
}

Load from File

go
func loadFromFile(filename string) (*cvss.Cvss3x, error) {
    jsonData, err := ioutil.ReadFile(filename)
    if err != nil {
        return nil, fmt.Errorf("file read failed: %w", err)
    }

    return safeLoadFromJSON(jsonData)
}

Batch File Operations

go
func saveBatchToFiles(vectors []*cvss.Cvss3x, directory string) error {
    // Create directory if it doesn't exist
    err := os.MkdirAll(directory, 0755)
    if err != nil {
        return err
    }

    for i, vector := range vectors {
        filename := filepath.Join(directory, fmt.Sprintf("vector_%03d.json", i+1))
        if err := saveToFile(vector, filename); err != nil {
            return fmt.Errorf("failed to save vector %d: %w", i+1, err)
        }
    }

    fmt.Printf("Saved %d vectors to %s\n", len(vectors), directory)
    return nil
}

func loadBatchFromFiles(directory string) ([]*cvss.Cvss3x, error) {
    files, err := filepath.Glob(filepath.Join(directory, "*.json"))
    if err != nil {
        return nil, err
    }

    var vectors []*cvss.Cvss3x
    for _, file := range files {
        vector, err := loadFromFile(file)
        if err != nil {
            fmt.Printf("Warning: failed to load %s: %v\n", file, err)
            continue
        }
        vectors = append(vectors, vector)
    }

    fmt.Printf("Loaded %d vectors from %s\n", len(vectors), directory)
    return vectors, nil
}

Custom JSON Formats

Simplified Export Format

go
type SimplifiedVector struct {
    Vector   string  `json:"cvss_vector"`
    Version  string  `json:"version"`
    Score    float64 `json:"base_score"`
    Severity string  `json:"severity"`
}

func exportSimplified(vector *cvss.Cvss3x) ([]byte, error) {
    calculator := cvss.NewCalculator(vector)
    score, err := calculator.Calculate()
    if err != nil {
        return nil, err
    }

    simplified := SimplifiedVector{
        Vector:   vector.String(),
        Version:  vector.GetVersion(),
        Score:    score,
        Severity: calculator.GetSeverityRating(score),
    }

    return json.MarshalIndent(simplified, "", "  ")
}

Detailed Analysis Format

go
type DetailedAnalysis struct {
    Vector      *cvss.Cvss3x `json:"vector"`
    Scores      ScoreBreakdown `json:"scores"`
    Analysis    VectorAnalysis `json:"analysis"`
    Timestamp   string `json:"timestamp"`
}

type ScoreBreakdown struct {
    Base         float64 `json:"base"`
    Temporal     float64 `json:"temporal,omitempty"`
    Environmental float64 `json:"environmental,omitempty"`
    Final        float64 `json:"final"`
}

type VectorAnalysis struct {
    Severity        string   `json:"severity"`
    RiskFactors     []string `json:"risk_factors"`
    Recommendations []string `json:"recommendations"`
    MetricSummary   map[string]string `json:"metric_summary"`
}

func exportDetailedAnalysis(vector *cvss.Cvss3x) ([]byte, error) {
    calculator := cvss.NewCalculator(vector)
    
    baseScore, _ := calculator.CalculateBaseScore()
    finalScore, _ := calculator.Calculate()
    
    analysis := DetailedAnalysis{
        Vector: vector,
        Scores: ScoreBreakdown{
            Base:  baseScore,
            Final: finalScore,
        },
        Analysis: VectorAnalysis{
            Severity:      calculator.GetSeverityRating(finalScore),
            RiskFactors:   analyzeRiskFactors(vector),
            Recommendations: generateRecommendations(vector),
            MetricSummary: summarizeMetrics(vector),
        },
        Timestamp: time.Now().Format(time.RFC3339),
    }

    // Add temporal score if present
    if vector.HasTemporal() {
        temporalScore, _ := calculator.CalculateTemporalScore()
        analysis.Scores.Temporal = temporalScore
    }

    // Add environmental score if present
    if vector.HasEnvironmental() {
        envScore, _ := calculator.CalculateEnvironmentalScore()
        analysis.Scores.Environmental = envScore
    }

    return json.MarshalIndent(analysis, "", "  ")
}

func analyzeRiskFactors(vector *cvss.Cvss3x) []string {
    var factors []string
    
    if vector.Cvss3xBase.AttackVector.GetShortValue() == 'N' {
        factors = append(factors, "Network accessible")
    }
    
    if vector.Cvss3xBase.AttackComplexity.GetShortValue() == 'L' {
        factors = append(factors, "Low attack complexity")
    }
    
    if vector.Cvss3xBase.PrivilegesRequired.GetShortValue() == 'N' {
        factors = append(factors, "No privileges required")
    }
    
    if vector.Cvss3xBase.UserInteraction.GetShortValue() == 'N' {
        factors = append(factors, "No user interaction required")
    }
    
    return factors
}

func generateRecommendations(vector *cvss.Cvss3x) []string {
    var recommendations []string
    
    if vector.Cvss3xBase.AttackVector.GetShortValue() == 'N' {
        recommendations = append(recommendations, "Implement network segmentation")
    }
    
    if vector.Cvss3xBase.PrivilegesRequired.GetShortValue() == 'N' {
        recommendations = append(recommendations, "Implement authentication controls")
    }
    
    if vector.Cvss3xBase.ConfidentialityImpact.GetShortValue() == 'H' {
        recommendations = append(recommendations, "Encrypt sensitive data")
    }
    
    return recommendations
}

func summarizeMetrics(vector *cvss.Cvss3x) map[string]string {
    summary := make(map[string]string)
    
    summary["Attack Vector"] = vector.Cvss3xBase.AttackVector.GetLongValue()
    summary["Attack Complexity"] = vector.Cvss3xBase.AttackComplexity.GetLongValue()
    summary["Privileges Required"] = vector.Cvss3xBase.PrivilegesRequired.GetLongValue()
    summary["User Interaction"] = vector.Cvss3xBase.UserInteraction.GetLongValue()
    summary["Scope"] = vector.Cvss3xBase.Scope.GetLongValue()
    summary["Confidentiality"] = vector.Cvss3xBase.ConfidentialityImpact.GetLongValue()
    summary["Integrity"] = vector.Cvss3xBase.IntegrityImpact.GetLongValue()
    summary["Availability"] = vector.Cvss3xBase.AvailabilityImpact.GetLongValue()
    
    return summary
}

API Integration

REST API Handler

go
func handleVectorAnalysis(w http.ResponseWriter, r *http.Request) {
    switch r.Method {
    case "POST":
        var request struct {
            Vector string `json:"vector"`
            Format string `json:"format,omitempty"`
        }

        if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
            http.Error(w, "Invalid JSON", http.StatusBadRequest)
            return
        }

        // Parse vector
        parser := parser.NewCvss3xParser(request.Vector)
        vector, err := parser.Parse()
        if err != nil {
            http.Error(w, fmt.Sprintf("Parse error: %v", err), http.StatusBadRequest)
            return
        }

        // Generate response based on format
        var responseData []byte
        switch request.Format {
        case "detailed":
            responseData, err = exportDetailedAnalysis(vector)
        case "simplified":
            responseData, err = exportSimplified(vector)
        default:
            responseData, err = json.MarshalIndent(vector, "", "  ")
        }

        if err != nil {
            http.Error(w, fmt.Sprintf("Export error: %v", err), http.StatusInternalServerError)
            return
        }

        w.Header().Set("Content-Type", "application/json")
        w.Write(responseData)

    default:
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
    }
}

Batch API Handler

go
func handleBatchAnalysis(w http.ResponseWriter, r *http.Request) {
    var request struct {
        Vectors []string `json:"vectors"`
        Format  string   `json:"format,omitempty"`
    }

    if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
        http.Error(w, "Invalid JSON", http.StatusBadRequest)
        return
    }

    var results []interface{}

    for i, vectorStr := range request.Vectors {
        result := map[string]interface{}{
            "index":  i,
            "vector": vectorStr,
        }

        parser := parser.NewCvss3xParser(vectorStr)
        vector, err := parser.Parse()
        if err != nil {
            result["error"] = err.Error()
            results = append(results, result)
            continue
        }

        calculator := cvss.NewCalculator(vector)
        score, _ := calculator.Calculate()

        switch request.Format {
        case "simplified":
            result["score"] = score
            result["severity"] = calculator.GetSeverityRating(score)
        default:
            result["parsed"] = vector
            result["score"] = score
            result["severity"] = calculator.GetSeverityRating(score)
        }

        results = append(results, result)
    }

    response := map[string]interface{}{
        "results": results,
        "total":   len(request.Vectors),
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(response)
}

Database Integration

SQL Database Storage

go
func saveVectorToDB(db *sql.DB, vector *cvss.Cvss3x) error {
    calculator := cvss.NewCalculator(vector)
    score, err := calculator.Calculate()
    if err != nil {
        return err
    }

    jsonData, err := json.Marshal(vector)
    if err != nil {
        return err
    }

    query := `
        INSERT INTO cvss_vectors (
            vector_string, 
            score, 
            severity, 
            json_data, 
            created_at
        ) VALUES (?, ?, ?, ?, ?)
    `

    _, err = db.Exec(query,
        vector.String(),
        score,
        calculator.GetSeverityRating(score),
        string(jsonData),
        time.Now(),
    )

    return err
}

func loadVectorFromDB(db *sql.DB, id int) (*cvss.Cvss3x, error) {
    var jsonData string
    query := `SELECT json_data FROM cvss_vectors WHERE id = ?`
    
    err := db.QueryRow(query, id).Scan(&jsonData)
    if err != nil {
        return nil, err
    }

    return safeLoadFromJSON([]byte(jsonData))
}

NoSQL Database Storage

go
func saveVectorToMongo(collection *mongo.Collection, vector *cvss.Cvss3x) error {
    calculator := cvss.NewCalculator(vector)
    score, _ := calculator.Calculate()

    document := struct {
        VectorString string      `bson:"vector_string"`
        Score        float64     `bson:"score"`
        Severity     string      `bson:"severity"`
        Vector       *cvss.Cvss3x `bson:"vector"`
        CreatedAt    time.Time   `bson:"created_at"`
    }{
        VectorString: vector.String(),
        Score:        score,
        Severity:     calculator.GetSeverityRating(score),
        Vector:       vector,
        CreatedAt:    time.Now(),
    }

    _, err := collection.InsertOne(context.Background(), document)
    return err
}

Performance Optimization

Streaming JSON

go
func streamVectorsToJSON(vectors []*cvss.Cvss3x, w io.Writer) error {
    encoder := json.NewEncoder(w)

    // Write array start
    w.Write([]byte("["))

    for i, vector := range vectors {
        if i > 0 {
            w.Write([]byte(","))
        }

        if err := encoder.Encode(vector); err != nil {
            return err
        }
    }

    // Write array end
    w.Write([]byte("]"))
    return nil
}

Memory-Efficient Processing

go
func processLargeJSONFile(filename string, processor func(*cvss.Cvss3x) error) error {
    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer file.Close()

    decoder := json.NewDecoder(file)

    // Read opening bracket
    _, err = decoder.Token()
    if err != nil {
        return err
    }

    // Process each vector
    for decoder.More() {
        var vector cvss.Cvss3x
        if err := decoder.Decode(&vector); err != nil {
            return err
        }

        if err := processor(&vector); err != nil {
            return err
        }
    }

    // Read closing bracket
    _, err = decoder.Token()
    return err
}

Testing and Validation

JSON Schema Validation

go
const cvssJSONSchema = `{
  "type": "object",
  "required": ["majorVersion", "minorVersion", "base"],
  "properties": {
    "majorVersion": {"type": "integer", "enum": [3]},
    "minorVersion": {"type": "integer", "enum": [0, 1]},
    "base": {"type": "object"}
  }
}`

func validateJSONSchema(jsonData []byte) error {
    schemaLoader := gojsonschema.NewStringLoader(cvssJSONSchema)
    documentLoader := gojsonschema.NewBytesLoader(jsonData)

    result, err := gojsonschema.Validate(schemaLoader, documentLoader)
    if err != nil {
        return err
    }

    if !result.Valid() {
        var errors []string
        for _, desc := range result.Errors() {
            errors = append(errors, desc.String())
        }
        return fmt.Errorf("validation errors: %s", strings.Join(errors, "; "))
    }

    return nil
}

Next Steps

After mastering JSON operations, you can explore:

Released under the MIT License.