Skip to content

examples/json-parser/main.go

:material-github: View on GitHub · 224 lines · live source, included at build time

package main

import (
    "context"
    "fmt"
    "log"
    "os"
    "time"

    "github.com/alikatgh/safeheaders-go/jsmn-go"
)

const (
    // MaxJSONSize limits input to prevent DoS attacks
    MaxJSONSize = 10 * 1024 * 1024 // 10MB

    // ParseTimeout limits parsing time
    ParseTimeout = 30 * time.Second
)

func main() {
    fmt.Println("SafeHeaders-Go JSON Parser Example")
    fmt.Print("===================================\n\n")

    // Example 1: Parse small JSON
    smallExample()

    // Example 2: Parse with validation
    validatedExample()

    // Example 3: Parse in parallel
    parallelExample()

    // Example 4: Handle errors gracefully
    errorHandlingExample()

    // Example 5: Production-ready parsing
    productionExample()
}

func smallExample() {
    fmt.Println("Example 1: Basic JSON Parsing")
    fmt.Println("------------------------------")

    json := []byte(`{
        "name": "SafeHeaders-Go",
        "version": "1.0.0",
        "stable": true,
        "modules": ["jsmn-go", "stb-image-go", "tinyxml2-go"]
    }`)

    p := jsmngo.NewParser(100)
    count, err := p.Parse(json)
    if err != nil {
        log.Fatalf("Parse failed: %v", err)
    }

    fmt.Printf("✅ Parsed %d tokens successfully\n", count)
    tokens := p.Tokens()
    fmt.Printf("   First token: Type=%v, Range=[%d:%d]\n\n",
        tokens[0].Type, tokens[0].Start, tokens[0].End)
}

func validatedExample() {
    fmt.Println("Example 2: Parse with Input Validation (NEW API)")
    fmt.Println("------------------------------------------------")

    json := []byte(`{"status": "ok", "data": [1, 2, 3]}`)

    // Use the new Config API with strict limits
    ctx, cancel := context.WithTimeout(context.Background(), ParseTimeout)
    defer cancel()

    config := jsmngo.StrictConfig() // 10MB limit, 100K tokens
    tokens, err := jsmngo.ParseWithConfig(ctx, json, config)
    if err != nil {
        log.Fatalf("❌ Parse failed: %v", err)
    }

    fmt.Printf("✅ Validated and parsed %d tokens\n", len(tokens))
    fmt.Printf("   Input size: %d bytes\n", len(json))
    fmt.Printf("   Config: MaxInputSize=%d, MaxTokens=%d\n\n",
        config.MaxInputSize, config.MaxTokens)
}

func parallelExample() {
    fmt.Println("Example 3: Parallel Parsing with Context (NEW API)")
    fmt.Println("-------------------------------------------------")

    // Generate a larger JSON array
    json := []byte(`[
        {"id": 1, "name": "Item 1", "value": 100},
        {"id": 2, "name": "Item 2", "value": 200},
        {"id": 3, "name": "Item 3", "value": 300},
        {"id": 4, "name": "Item 4", "value": 400},
        {"id": 5, "name": "Item 5", "value": 500}
    ]`)

    ctx, cancel := context.WithTimeout(context.Background(), ParseTimeout)
    defer cancel()

    start := time.Now()
    // Use new context-aware API
    tokens, err := jsmngo.ParseParallelWithContext(ctx, json)
    elapsed := time.Since(start)

    if err != nil {
        log.Fatalf("❌ Parallel parse failed: %v", err)
    }

    fmt.Printf("✅ Parsed %d tokens in %v (parallel, context-aware)\n", len(tokens), elapsed)
    fmt.Printf("   Input size: %d bytes\n", len(json))
    fmt.Printf("   Context: Timeout protection enabled\n\n")
}

func errorHandlingExample() {
    fmt.Println("Example 4: Error Handling")
    fmt.Println("-------------------------")

    malformedInputs := []struct {
        name string
        json string
    }{
        {"Unclosed object", `{"key": "value"`},
        {"Invalid character", `{"key": @}`},
        {"Trailing comma", `{"key": "value",}`},
        {"Empty input", ``},
    }

    for _, test := range malformedInputs {
        p := jsmngo.NewParser(100)
        _, err := p.Parse([]byte(test.json))
        if err != nil {
            fmt.Printf("✅ Correctly rejected: %s\n   Error: %v\n", test.name, err)
        } else {
            fmt.Printf("⚠️  Unexpectedly accepted: %s\n", test.name)
        }
    }
    fmt.Println()
}

func productionExample() {
    fmt.Println("Example 5: Production-Ready Parsing")
    fmt.Println("------------------------------------")

    // Simulate reading from file or API
    jsonData := []byte(`{
        "timestamp": "2025-01-01T00:00:00Z",
        "status": "success",
        "results": [
            {"id": 1, "score": 95.5},
            {"id": 2, "score": 87.2},
            {"id": 3, "score": 92.8}
        ],
        "metadata": {
            "count": 3,
            "version": "2.0"
        }
    }`)

    result, err := parseJSON(jsonData)
    if err != nil {
        log.Fatalf("❌ Production parse failed: %v", err)
    }

    fmt.Printf("✅ Production parse successful:\n")
    fmt.Printf("   Tokens: %d\n", result.TokenCount)
    fmt.Printf("   Parse time: %v\n", result.Duration)
    fmt.Printf("   Input size: %d bytes\n", result.InputSize)
    fmt.Printf("   Method: %s\n\n", result.Method)
}

// ParseResult contains parsing statistics
type ParseResult struct {
    TokenCount int
    Duration   time.Duration
    InputSize  int
    Method     string
}

// parseJSON is a production-ready JSON parsing function with:
// - Input validation with configurable limits
// - Size and token limits
// - Timeout protection
// - Automatic parallel mode for large inputs
// - Context cancellation support
func parseJSON(data []byte) (*ParseResult, error) {
    start := time.Now()

    // Use the new Config API for production safety
    config := jsmngo.DefaultConfig()
    config.MaxInputSize = MaxJSONSize
    config.ParallelThreshold = 4 * 1024 // 4KB

    // Create context with timeout
    ctx, cancel := context.WithTimeout(context.Background(), ParseTimeout)
    defer cancel()

    // Parse with validation and automatic strategy selection
    tokens, err := jsmngo.ParseWithConfig(ctx, data, config)
    if err != nil {
        return nil, fmt.Errorf("parse failed: %w", err)
    }

    // Determine which method was used
    method := "serial"
    if len(data) >= config.ParallelThreshold {
        method = "parallel (context-aware)"
    }

    return &ParseResult{
        TokenCount: len(tokens),
        Duration:   time.Since(start),
        InputSize:  len(data),
        Method:     method,
    }, nil
}

// init sets up the example
func init() {
    // In production, configure logging
    log.SetOutput(os.Stdout)
    log.SetFlags(log.Ltime | log.Lshortfile)
}