Skip to content

FxJSON use guidelines (English)

CloudZA edited this page Aug 18, 2025 · 3 revisions

FxJSON Wiki - Complete Documentation

Table of Contents

  1. Introduction
  2. Installation & Setup
  3. Core Concepts
  4. Performance Optimization
  5. Advanced Features
  6. Best Practices
  7. Error Handling
  8. Troubleshooting
  9. Migration Guide
  10. API Reference

Introduction

FxJSON is a high-performance JSON parsing library for Go that focuses on speed, memory efficiency, and ease of use. It provides significant performance improvements over the standard library while maintaining memory safety and offering a rich set of features.

Key Features

  • 🔥 High Performance: 20x-67x faster traversal operations
  • ⚡ Memory Efficient: Zero-allocation core operations
  • 🛡️ Memory Safety: Complete boundary checking
  • 🎯 Easy to Use: Intuitive chainable API
  • 🌐 Unicode Support: Perfect support for Chinese, emoji, and Unicode characters
  • 🧩 Nested JSON Expansion: Automatic recognition and expansion of nested JSON strings
  • 🔍 Advanced Querying: SQL-style conditional queries and filtering
  • 📊 Data Aggregation: Built-in statistical and aggregation functions
  • 💾 Smart Caching: High-performance caching with LRU eviction
  • 🔧 Debug Tools: Enhanced debugging and analysis features

Performance Comparison

Operation FxJSON Standard Library Performance Gain Memory Advantage
ForEach Traversal 104.7 ns 2115 ns 20.2x Zero allocations vs 57 allocs
Array Traversal 30.27 ns 2044 ns 67.5x Zero allocations vs 57 allocs
Deep Traversal 1363 ns 2787 ns 2.0x 29 allocs vs 83 allocs
Complex Traversal 1269 ns 3280 ns 2.6x Zero allocations vs 104 allocs

Why FxJSON?

// Traditional way with encoding/json
type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

data := []byte(`{"name":"Alice","age":30}`)
var user User
json.Unmarshal(data, &user) // Slow, lots of memory allocations

// FxJSON way - much faster!
node := fxjson.FromBytes(data)
name := node.Get("name").StringOr("Unknown") // Clean and fast
age := node.Get("age").IntOr(0)              // With default values!

Getting Started

Installation

# Just one command to get started
go get github.com/icloudza/fxjson

Your First FxJSON Program

package main

import (
    "fmt"
    "github.com/icloudza/fxjson"
)

func main() {
    // Let's parse some JSON!
    jsonData := []byte(`{
        "message": "Hello, World!",
        "timestamp": 1234567890,
        "success": true
    }`)
    
    // Create a node from JSON bytes
    node := fxjson.FromBytes(jsonData)
    
    // Extract values - it's that simple!
    message := node.Get("message").StringOr("No message")
    timestamp := node.Get("timestamp").IntOr(0)
    success := node.Get("success").BoolOr(false)
    
    fmt.Printf("Message: %s at time %d (success: %v)\n", 
               message, timestamp, success)
}

Core Concepts

1. The Node - Your JSON Swiss Army Knife

A Node is the core type in FxJSON. It represents any JSON value - object, array, string, number, boolean, or null.

// A Node can be anything in JSON
jsonData := []byte(`{
    "string": "Hello",
    "number": 42,
    "boolean": true,
    "null": null,
    "array": [1, 2, 3],
    "object": {"nested": "value"}
}`)

node := fxjson.FromBytes(jsonData)

// Each Get() returns another Node
stringNode := node.Get("string")   // Node representing "Hello"
numberNode := node.Get("number")   // Node representing 42
arrayNode := node.Get("array")     // Node representing [1,2,3]
objectNode := node.Get("object")   // Node representing {"nested":"value"}

2. Safe Value Extraction

FxJSON provides two ways to extract values:

// Method 1: Traditional with error handling
value, err := node.Get("key").String()
if err != nil {
    // Handle error
    value = "default"
}

// Method 2: Modern with default values (recommended!)
value := node.Get("key").StringOr("default") // Clean, simple, safe

3. Path Navigation

Navigate complex JSON structures easily:

jsonData := []byte(`{
    "user": {
        "profile": {
            "address": {
                "city": "Beijing"
            }
        }
    }
}`)

node := fxjson.FromBytes(jsonData)

// Instead of multiple Get() calls
city1 := node.Get("user").Get("profile").Get("address").Get("city").StringOr("")

// Use GetPath() for cleaner code
city2 := node.GetPath("user.profile.address.city").StringOr("")

// Access array elements in paths
jsonData2 := []byte(`{"users": [{"name": "Alice"}, {"name": "Bob"}]}`)
node2 := fxjson.FromBytes(jsonData2)
secondUser := node2.GetPath("users[1].name").StringOr("") // "Bob"

Basic Operations

Working with Objects

// Sample data: user information
userJSON := []byte(`{
    "id": 12345,
    "username": "alice_wonder",
    "email": "alice@example.com",
    "profile": {
        "firstName": "Alice",
        "lastName": "Wonder",
        "age": 28,
        "interests": ["coding", "reading", "hiking"]
    },
    "settings": {
        "theme": "dark",
        "notifications": true,
        "language": "en"
    }
}`)

user := fxjson.FromBytes(userJSON)

// Basic field access
username := user.Get("username").StringOr("anonymous")
email := user.Get("email").StringOr("")

// Nested field access
firstName := user.Get("profile").Get("firstName").StringOr("")
// Or use path for cleaner code
lastName := user.GetPath("profile.lastName").StringOr("")

// Check if fields exist
if user.HasKey("email") {
    fmt.Println("User has email")
}

// Get all keys from an object
settingsKeys := user.Get("settings").GetAllKeys()
// Result: ["theme", "notifications", "language"]

// Convert to Go map for easier manipulation
settingsMap := user.Get("settings").ToMap()
for key, value := range settingsMap {
    fmt.Printf("%s: %v\n", key, value.Raw())
}

Working with Arrays

// Sample data: shopping cart
cartJSON := []byte(`{
    "items": [
        {"id": 1, "name": "Laptop", "price": 999.99, "qty": 1},
        {"id": 2, "name": "Mouse", "price": 29.99, "qty": 2},
        {"id": 3, "name": "Keyboard", "price": 79.99, "qty": 1}
    ],
    "discounts": [10, 15, 5],
    "tags": ["electronics", "computers", "accessories"]
}`)

cart := fxjson.FromBytes(cartJSON)
items := cart.Get("items")

// Get array length
itemCount := items.Len() // 3

// Access by index
firstItem := items.Index(0)
firstItemName := firstItem.Get("name").StringOr("")

// Iterate through array - the fast way!
var totalPrice float64
items.ArrayForEach(func(index int, item fxjson.Node) bool {
    price := item.Get("price").FloatOr(0)
    qty := item.Get("qty").IntOr(1)
    totalPrice += price * float64(qty)
    
    fmt.Printf("Item %d: %s - $%.2f x %d\n", 
               index+1, 
               item.Get("name").StringOr(""),
               price, qty)
    
    return true // continue iteration
})

// Array operations
discounts := cart.Get("discounts")
firstDiscount := discounts.First()       // First element
lastDiscount := discounts.Last()         // Last element
topTwo := discounts.Slice(0, 2)         // First two elements

// Convert to Go slice
if discountSlice, err := discounts.ToIntSlice(); err == nil {
    maxDiscount := int64(0)
    for _, d := range discountSlice {
        if d > maxDiscount {
            maxDiscount = d
        }
    }
    fmt.Printf("Max discount: %d%%\n", maxDiscount)
}

Type Checking and Conversion

// Mixed type JSON
mixedJSON := []byte(`{
    "string": "hello",
    "integer": 42,
    "float": 3.14,
    "boolean": true,
    "null": null,
    "array": [1, 2, 3],
    "object": {"key": "value"}
}`)

node := fxjson.FromBytes(mixedJSON)

// Check types before conversion
fields := []string{"string", "integer", "float", "boolean", "null", "array", "object"}

for _, field := range fields {
    n := node.Get(field)
    
    // Multiple ways to check type
    switch {
    case n.IsString():
        fmt.Printf("%s is a string: %s\n", field, n.StringOr(""))
    case n.IsNumber():
        // Numbers can be int or float
        if n.IsInteger() {
            fmt.Printf("%s is an integer: %d\n", field, n.IntOr(0))
        } else {
            fmt.Printf("%s is a float: %.2f\n", field, n.FloatOr(0))
        }
    case n.IsBool():
        fmt.Printf("%s is a boolean: %v\n", field, n.BoolOr(false))
    case n.IsNull():
        fmt.Printf("%s is null\n", field)
    case n.IsArray():
        fmt.Printf("%s is an array with %d items\n", field, n.Len())
    case n.IsObject():
        fmt.Printf("%s is an object with keys: %v\n", field, n.GetAllKeys())
    }
}

// Safe type conversion with defaults
anyValue := node.Get("unknown_field")
safeString := anyValue.StringOr("default")    // Won't panic
safeInt := anyValue.IntOr(0)                  // Safe with default
safeFloat := anyValue.FloatOr(0.0)            // Always safe
safeBool := anyValue.BoolOr(false)            // No errors!

Advanced Features

1. Data Validation - Built-in Validators

// User registration data
registrationJSON := []byte(`{
    "email": "user@example.com",
    "alternativeEmail": "invalid-email",
    "website": "https://example.com",
    "ipAddress": "192.168.1.100",
    "phone": "+1234567890",
    "userId": "550e8400-e29b-41d4-a716-446655440000"
}`)

reg := fxjson.FromBytes(registrationJSON)

// Email validation
if reg.Get("email").IsValidEmail() {
    fmt.Println("✅ Primary email is valid")
}

if !reg.Get("alternativeEmail").IsValidEmail() {
    fmt.Println("❌ Alternative email is invalid")
}

// URL validation
if reg.Get("website").IsValidURL() {
    fmt.Println("✅ Website URL is valid")
}

// IP address validation
if reg.Get("ipAddress").IsValidIPv4() {
    fmt.Println("✅ IPv4 address is valid")
}

// Phone validation (E.164 format)
if reg.Get("phone").IsValidPhone() {
    fmt.Println("✅ Phone number is valid")
}

// UUID validation
if reg.Get("userId").IsValidUUID() {
    fmt.Println("✅ User ID is a valid UUID")
}

// Custom validation logic
password := reg.Get("password").StringOr("")
if len(password) < 8 {
    fmt.Println("❌ Password too short")
}

2. Batch Operations - Process Multiple Fields Efficiently

// Order data with many fields
orderJSON := []byte(`{
    "order": {
        "id": "ORD-2024-001",
        "customer": {
            "id": "CUST-123",
            "name": "John Doe",
            "email": "john@example.com",
            "phone": "+1234567890",
            "address": {
                "street": "123 Main St",
                "city": "New York",
                "country": "USA"
            }
        },
        "items": [
            {"sku": "LAPTOP-01", "qty": 1, "price": 999.99},
            {"sku": "MOUSE-02", "qty": 2, "price": 29.99}
        ],
        "totals": {
            "subtotal": 1059.97,
            "tax": 95.40,
            "shipping": 15.00,
            "total": 1170.37
        }
    }
}`)

order := fxjson.FromBytes(orderJSON)

// Get multiple values at once - super efficient!
values := order.GetMultiple(
    "order.id",
    "order.customer.name",
    "order.customer.email",
    "order.totals.total",
)

// Extract all values with defaults
orderId := values[0].StringOr("N/A")
customerName := values[1].StringOr("Guest")
customerEmail := values[2].StringOr("")
totalAmount := values[3].FloatOr(0.0)

fmt.Printf("Order %s for %s (%.2f)\n", orderId, customerName, totalAmount)

// Check if all required fields exist
requiredFields := []string{
    "order.id",
    "order.customer.id",
    "order.customer.email",
    "order.items",
    "order.totals.total",
}

if order.HasAllPaths(requiredFields...) {
    fmt.Println("✅ All required fields present")
} else {
    fmt.Println("❌ Missing required fields")
}

// Check if customer has any contact method
contactMethods := []string{
    "order.customer.email",
    "order.customer.phone",
    "order.customer.mobile",
    "order.customer.fax",
}

if order.HasAnyPath(contactMethods...) {
    fmt.Println("✅ Customer has at least one contact method")
}

3. Search and Filter - Find What You Need

// Product catalog
catalogJSON := []byte(`{
    "products": [
        {"id": 1, "name": "Laptop", "category": "Electronics", "price": 999, "inStock": true},
        {"id": 2, "name": "Desk", "category": "Furniture", "price": 299, "inStock": true},
        {"id": 3, "name": "Monitor", "category": "Electronics", "price": 399, "inStock": false},
        {"id": 4, "name": "Chair", "category": "Furniture", "price": 199, "inStock": true},
        {"id": 5, "name": "Keyboard", "category": "Electronics", "price": 79, "inStock": true}
    ]
}`)

catalog := fxjson.FromBytes(catalogJSON)
products := catalog.Get("products")

// Find first electronics product
_, firstElectronics, found := products.FindInArray(func(idx int, product fxjson.Node) bool {
    return product.Get("category").StringOr("") == "Electronics"
})

if found {
    name := firstElectronics.Get("name").StringOr("")
    price := firstElectronics.Get("price").IntOr(0)
    fmt.Printf("First electronics item: %s ($%d)\n", name, price)
}

// Filter all products in stock
inStockProducts := products.FilterArray(func(idx int, product fxjson.Node) bool {
    return product.Get("inStock").BoolOr(false)
})

fmt.Printf("Products in stock: %d\n", len(inStockProducts))
for _, product := range inStockProducts {
    fmt.Printf("- %s\n", product.Get("name").StringOr(""))
}

// Count products under $500
affordableCount := products.CountIf(func(idx int, product fxjson.Node) bool {
    return product.Get("price").IntOr(0) < 500
})
fmt.Printf("Affordable products (< $500): %d\n", affordableCount)

// Check if all products have required fields
allValid := products.AllMatch(func(idx int, product fxjson.Node) bool {
    return product.HasKey("id") && 
           product.HasKey("name") && 
           product.HasKey("price")
})

if allValid {
    fmt.Println("✅ All products have required fields")
}

// Check if any product is out of stock
hasOutOfStock := products.AnyMatch(func(idx int, product fxjson.Node) bool {
    return !product.Get("inStock").BoolOr(true)
})

if hasOutOfStock {
    fmt.Println("⚠️ Some products are out of stock")
}

4. Deep Traversal - Walk the Entire Tree

// Complex nested structure
companyJSON := []byte(`{
    "company": {
        "name": "TechCorp",
        "departments": [
            {
                "name": "Engineering",
                "teams": [
                    {
                        "name": "Backend",
                        "members": [
                            {"name": "Alice", "role": "Lead"},
                            {"name": "Bob", "role": "Senior"}
                        ]
                    },
                    {
                        "name": "Frontend",
                        "members": [
                            {"name": "Charlie", "role": "Senior"},
                            {"name": "Diana", "role": "Junior"}
                        ]
                    }
                ]
            },
            {
                "name": "Marketing",
                "teams": [
                    {
                        "name": "Digital",
                        "members": [
                            {"name": "Eve", "role": "Manager"}
                        ]
                    }
                ]
            }
        ]
    }
}`)

company := fxjson.FromBytes(companyJSON)

// Walk through entire JSON structure
fmt.Println("Company Structure:")
company.Walk(func(path string, node fxjson.Node) bool {
    // Only print leaf nodes (actual values)
    if node.IsScalar() {
        indent := strings.Count(path, ".") + strings.Count(path, "[")
        fmt.Printf("%s%s = %v\n", 
                   strings.Repeat("  ", indent), 
                   path, 
                   node.Raw())
    }
    return true // continue walking
})

// Count all employees
employeeCount := 0
company.Walk(func(path string, node fxjson.Node) bool {
    if strings.Contains(path, "members[") && strings.HasSuffix(path, ".name") {
        employeeCount++
    }
    return true
})
fmt.Printf("\nTotal employees: %d\n", employeeCount)

// Find all team names
var teamNames []string
company.Walk(func(path string, node fxjson.Node) bool {
    if strings.Contains(path, "teams[") && strings.HasSuffix(path, ".name") {
        if name, err := node.String(); err == nil {
            teamNames = append(teamNames, name)
        }
    }
    return true
})
fmt.Printf("Teams: %v\n", teamNames)

5. String Operations - Built-in Text Processing

// Article metadata
articleJSON := []byte(`{
    "title": "  Introduction to Go Programming  ",
    "slug": "intro-to-go",
    "description": "Learn the BASICS of Go programming language",
    "tags": "golang,programming,tutorial",
    "url": "https://example.com/articles/intro-to-go",
    "filename": "intro_to_go.pdf"
}`)

article := fxjson.FromBytes(articleJSON)

// String manipulation
title := article.Get("title")
trimmedTitle, _ := title.Trim()           // Remove whitespace
upperTitle, _ := title.ToUpper()          // Convert to uppercase
lowerTitle, _ := title.ToLower()          // Convert to lowercase

fmt.Printf("Original: '%s'\n", title.StringOr(""))
fmt.Printf("Trimmed: '%s'\n", trimmedTitle)
fmt.Printf("Upper: '%s'\n", upperTitle)
fmt.Printf("Lower: '%s'\n", lowerTitle)

// String checking
url := article.Get("url")
if url.Contains("example.com") {
    fmt.Println("✅ URL is from example.com")
}

if url.StartsWith("https://") {
    fmt.Println("✅ URL uses HTTPS")
}

filename := article.Get("filename")
if filename.EndsWith(".pdf") {
    fmt.Println("✅ File is a PDF")
}

// Working with tags
tags := article.Get("tags").StringOr("")
tagList := strings.Split(tags, ",")
fmt.Printf("Tags: %v\n", tagList)

6. Object Operations - Merge, Pick, and Omit

// Configuration management
defaultConfigJSON := []byte(`{
    "database": {
        "host": "localhost",
        "port": 5432,
        "name": "myapp",
        "pool": 10
    },
    "cache": {
        "enabled": true,
        "ttl": 3600
    },
    "logging": {
        "level": "info",
        "format": "json"
    }
}`)

userConfigJSON := []byte(`{
    "database": {
        "host": "db.production.com",
        "port": 5432,
        "user": "admin",
        "password": "secret"
    },
    "cache": {
        "ttl": 7200
    }
}`)

defaultConfig := fxjson.FromBytes(defaultConfigJSON)
userConfig := fxjson.FromBytes(userConfigJSON)

// Merge configurations (user config overrides defaults)
dbDefaults := defaultConfig.Get("database")
dbUser := userConfig.Get("database")
finalDb := dbDefaults.Merge(dbUser)

// Pick only safe fields (exclude sensitive data)
safeDb := dbUser.Pick("host", "port", "name")
fmt.Println("Safe to log:", safeDb)

// Omit sensitive fields
publicDb := dbUser.Omit("password", "user")
fmt.Println("Public config:", publicDb)

// Complex merge scenario
baseSettings := fxjson.FromBytes([]byte(`{
    "theme": "light",
    "language": "en",
    "notifications": {
        "email": true,
        "push": false,
        "sms": false
    }
}`))

userSettings := fxjson.FromBytes([]byte(`{
    "theme": "dark",
    "notifications": {
        "push": true
    }
}`))

// Merge preserves structure
finalSettings := baseSettings.Merge(userSettings)
// Result: theme="dark", language="en", notifications={email:true, push:true, sms:false}

Performance Optimization

1. High-Performance Traversal

// Large dataset
largeJSON := []byte(`{
    "users": [
        {"id": 1, "name": "User1", "score": 85, "active": true},
        {"id": 2, "name": "User2", "score": 92, "active": false},
        // ... imagine thousands more ...
    ]
}`)

data := fxjson.FromBytes(largeJSON)
users := data.Get("users")

// DON'T do this - creates intermediate nodes
var totalScore int64
for i := 0; i < users.Len(); i++ {
    user := users.Index(i) // Creates a new node each time
    score := user.Get("score").IntOr(0)
    totalScore += score
}

// DO this instead - zero allocations!
var betterTotalScore int64
users.ArrayForEach(func(index int, user fxjson.Node) bool {
    betterTotalScore += user.Get("score").IntOr(0)
    return true // Continue iteration
})

// For objects, use ForEach for maximum speed
userObj := users.Index(0)
userObj.ForEach(func(key string, value fxjson.Node) bool {
    // Process each field with zero allocations
    fmt.Printf("%s: %s\n", key, value.Raw())
    return true
})

2. Efficient Struct Decoding

type User struct {
    ID       int      `json:"id"`
    Username string   `json:"username"`
    Email    string   `json:"email"`
    Tags     []string `json:"tags"`
    Profile  struct {
        Bio      string `json:"bio"`
        Location string `json:"location"`
    } `json:"profile"`
}

userJSON := []byte(`{
    "id": 123,
    "username": "johndoe",
    "email": "john@example.com",
    "tags": ["developer", "gopher"],
    "profile": {
        "bio": "Go enthusiast",
        "location": "San Francisco"
    }
}`)

// Good - Node-based decoding
node := fxjson.FromBytes(userJSON)
var user1 User
err := node.Decode(&user1)

// Better - Direct decoding
var user2 User
err = fxjson.DecodeStruct(userJSON, &user2)

// Best - Ultra-fast decoding
var user3 User
err = fxjson.DecodeStructFast(userJSON, &user3)

// Performance comparison:
// node.Decode()        - 1x speed (baseline)
// DecodeStruct()       - 1.5x speed
// DecodeStructFast()   - 2x speed

3. Caching for Repeated Parsing

import (
    "time"
    "github.com/icloudza/fxjson"
)

// Enable caching for frequently parsed JSON
cache := fxjson.NewMemoryCache(100) // Cache up to 100 items
fxjson.EnableCaching(cache)

// First parse - goes to cache
start := time.Now()
node1 := fxjson.FromBytesWithCache(userJSON, 5*time.Minute)
firstTime := time.Since(start)

// Second parse - from cache (much faster!)
start = time.Now()
node2 := fxjson.FromBytesWithCache(userJSON, 5*time.Minute)
cachedTime := time.Since(start)

fmt.Printf("First parse: %v\n", firstTime)
fmt.Printf("Cached parse: %v (%.1fx faster)\n", 
           cachedTime, float64(firstTime)/float64(cachedTime))

// Check cache statistics
stats := cache.Stats()
fmt.Printf("Cache hit rate: %.1f%%\n", stats.HitRate*100)

// Disable caching when done
fxjson.DisableCaching()

Real-world Examples

Example 1: REST API Response Handler

// Typical API response
type APIResponse struct {
    Success bool        `json:"success"`
    Data    interface{} `json:"data"`
    Error   *string     `json:"error"`
    Meta    struct {
        Page    int `json:"page"`
        PerPage int `json:"per_page"`
        Total   int `json:"total"`
    } `json:"meta"`
}

func handleAPIResponse(responseBody []byte) {
    node := fxjson.FromBytes(responseBody)
    
    // Quick success check
    if !node.Get("success").BoolOr(false) {
        errorMsg := node.Get("error").StringOr("Unknown error")
        log.Printf("API Error: %s", errorMsg)
        return
    }
    
    // Process data based on type
    data := node.Get("data")
    
    if data.IsArray() {
        // Handle list response
        fmt.Printf("Received %d items\n", data.Len())
        
        data.ArrayForEach(func(i int, item fxjson.Node) bool {
            // Process each item
            id := item.Get("id").IntOr(0)
            name := item.Get("name").StringOr("")
            fmt.Printf("Item %d: %s\n", id, name)
            return true
        })
        
    } else if data.IsObject() {
        // Handle single item response
        id := data.Get("id").IntOr(0)
        fmt.Printf("Single item ID: %d\n", id)
    }
    
    // Check pagination
    if node.HasKey("meta") {
        currentPage := node.GetPath("meta.page").IntOr(1)
        totalPages := node.GetPath("meta.total").IntOr(0) / 
                     node.GetPath("meta.per_page").IntOr(10)
        
        fmt.Printf("Page %d of %d\n", currentPage, totalPages)
    }
}

Example 2: Configuration File Manager

type AppConfig struct {
    Server struct {
        Host string
        Port int
        SSL  bool
    }
    Database struct {
        Driver   string
        Host     string
        Port     int
        Name     string
        User     string
        Password string
        Pool     int
    }
    Features map[string]bool
}

func loadConfig(env string) (*AppConfig, error) {
    // Load base config
    baseConfig, err := os.ReadFile("config/base.json")
    if err != nil {
        return nil, err
    }
    
    // Load environment-specific config
    envConfig, err := os.ReadFile(fmt.Sprintf("config/%s.json", env))
    if err != nil {
        // Environment config is optional
        envConfig = []byte("{}")
    }
    
    // Parse configs
    base := fxjson.FromBytes(baseConfig)
    envSpecific := fxjson.FromBytes(envConfig)
    
    // Merge configurations
    // Environment-specific values override base values
    final := base.Merge(envSpecific)
    
    // Validate required fields
    requiredFields := []string{
        "server.host",
        "server.port",
        "database.driver",
        "database.host",
        "database.name",
    }
    
    for _, field := range requiredFields {
        if !final.HasKey(field) {
            return nil, fmt.Errorf("missing required field: %s", field)
        }
    }
    
    // Decode to struct
    var config AppConfig
    if err := final.Decode(&config); err != nil {
        return nil, err
    }
    
    // Apply defaults for optional fields
    if config.Database.Pool == 0 {
        config.Database.Pool = 10 // Default pool size
    }
    
    if config.Server.Port == 0 {
        config.Server.Port = 8080 // Default port
    }
    
    return &config, nil
}

Example 3: Data Migration Tool

// Migrate old format to new format
func migrateUserData(oldJSON []byte) ([]byte, error) {
    old := fxjson.FromBytes(oldJSON)
    
    // Build new structure
    newData := map[string]interface{}{
        "version": "2.0",
        "user": map[string]interface{}{
            "id":       old.Get("user_id").IntOr(0),
            "username": old.Get("user_name").StringOr(""),
            "email":    old.Get("email_address").StringOr(""),
            "profile": map[string]interface{}{
                "firstName": old.Get("first_name").StringOr(""),
                "lastName":  old.Get("last_name").StringOr(""),
                "avatar":    old.Get("profile_pic").StringOr(""),
                "bio":       old.Get("about").StringOr(""),
            },
            "settings": map[string]interface{}{
                "notifications": map[string]interface{}{
                    "email": old.Get("email_notifications").BoolOr(true),
                    "push":  old.Get("push_notifications").BoolOr(false),
                },
                "privacy": map[string]interface{}{
                    "profileVisible": old.Get("public_profile").BoolOr(true),
                    "showEmail":      old.Get("show_email").BoolOr(false),
                },
            },
            "metadata": map[string]interface{}{
                "createdAt":  old.Get("created_date").StringOr(""),
                "modifiedAt": old.Get("modified_date").StringOr(""),
                "lastLogin":  old.Get("last_login").StringOr(""),
            },
        },
    }
    
    // Handle arrays differently
    if old.HasKey("tags") {
        tags := old.Get("tags")
        if tags.IsString() {
            // Old format: comma-separated string
            tagStr := tags.StringOr("")
            newData["user"].(map[string]interface{})["tags"] = strings.Split(tagStr, ",")
        } else if tags.IsArray() {
            // Already in new format
            if tagSlice, err := tags.ToStringSlice(); err == nil {
                newData["user"].(map[string]interface{})["tags"] = tagSlice
            }
        }
    }
    
    // Convert back to JSON
    return json.Marshal(newData)
}

Example 4: Log Analyzer

// Analyze JSON logs
func analyzeJSONLogs(logData []byte) {
    // Assume each line is a JSON object
    lines := strings.Split(string(logData), "\n")
    
    var (
        errorCount   int
        warnCount    int
        infoCount    int
        totalLatency float64
        requests     int
    )
    
    errorMessages := make(map[string]int)
    
    for _, line := range lines {
        if line == "" {
            continue
        }
        
        log := fxjson.FromBytes([]byte(line))
        
        // Count log levels
        level := log.Get("level").StringOr("")
        switch level {
        case "ERROR":
            errorCount++
            // Track error messages
            msg := log.Get("message").StringOr("")
            errorMessages[msg]++
        case "WARN":
            warnCount++
        case "INFO":
            infoCount++
        }
        
        // Analyze performance metrics
        if log.HasKey("latency") {
            latency := log.Get("latency").FloatOr(0)
            totalLatency += latency
            requests++
        }
        
        // Check for specific patterns
        if log.Get("status").IntOr(0) >= 500 {
            fmt.Printf("Server error at %s: %s\n",
                log.Get("timestamp").StringOr(""),
                log.Get("message").StringOr(""))
        }
    }
    
    // Print summary
    fmt.Printf("\n=== Log Analysis Summary ===\n")
    fmt.Printf("Total logs: %d\n", len(lines))
    fmt.Printf("Errors: %d, Warnings: %d, Info: %d\n", 
               errorCount, warnCount, infoCount)
    
    if requests > 0 {
        avgLatency := totalLatency / float64(requests)
        fmt.Printf("Average latency: %.2fms\n", avgLatency)
    }
    
    if len(errorMessages) > 0 {
        fmt.Printf("\nTop errors:\n")
        for msg, count := range errorMessages {
            if count > 1 {
                fmt.Printf("  - %s (occurred %d times)\n", msg, count)
            }
        }
    }
}

Troubleshooting

Common Issues and Solutions

1. Empty String Handling

// Problem: Empty strings might cause issues
jsonData := []byte(`{"name": "", "description": null}`)
node := fxjson.FromBytes(jsonData)

// Solution: Always use default values
name := node.Get("name").StringOr("Anonymous")
description := node.Get("description").StringOr("No description")

// Check if truly empty
if node.Get("name").IsEmpty() {
    fmt.Println("Name is empty")
}

2. Number Precision

// Problem: Floating point precision
jsonData := []byte(`{"price": 19.99, "tax": 1.995}`)
node := fxjson.FromBytes(jsonData)

// Solution 1: Use FloatString() to preserve original format
priceStr, _ := node.Get("price").FloatString() // "19.99"

// Solution 2: Use proper rounding for calculations
price := node.Get("price").FloatOr(0)
tax := node.Get("tax").FloatOr(0)
total := math.Round((price+tax)*100) / 100 // Proper rounding

3. Nested JSON Strings

// Problem: JSON within JSON strings
jsonData := []byte(`{
    "data": "{\"nested\":\"value\",\"count\":42}"
}`)

// FxJSON automatically expands nested JSON!
node := fxjson.FromBytes(jsonData)
nested := node.Get("data").Get("nested").StringOr("") // Works!
count := node.Get("data").Get("count").IntOr(0)       // 42

4. Large Arrays

// Problem: Processing large arrays efficiently
largeArray := []byte(`{"items": [/* thousands of items */]}`)

// Solution: Use streaming approach
node := fxjson.FromBytes(largeArray)
items := node.Get("items")

// Process in batches
batchSize := 100
for i := 0; i < items.Len(); i += batchSize {
    end := i + batchSize
    if end > items.Len() {
        end = items.Len()
    }
    
    // Process batch
    for j := i; j < end; j++ {
        item := items.Index(j)
        // Process item
    }
    
    // Could add sleep here to prevent overwhelming system
    // time.Sleep(10 * time.Millisecond)
}

Best Practices

1. Always Use Default Values

// ❌ Don't do this
value, err := node.Get("key").String()
if err != nil {
    // Handle error
}

// ✅ Do this instead
value := node.Get("key").StringOr("default")

2. Prefer Path Access for Deep Nesting

// ❌ Verbose and error-prone
city := node.Get("user").Get("address").Get("city").StringOr("")

// ✅ Clean and readable
city := node.GetPath("user.address.city").StringOr("")

3. Use Type Checking Before Conversion

// ✅ Safe approach
field := node.Get("unknown_field")
if field.IsNumber() {
    value := field.IntOr(0)
    // Use value
} else if field.IsString() {
    value := field.StringOr("")
    // Use value
}

4. Validate Before Processing

// ✅ Always validate structure
requiredFields := []string{"id", "name", "email"}
if !node.HasAllPaths(requiredFields...) {
    return errors.New("missing required fields")
}

// Then process safely
id := node.Get("id").IntOr(0)
name := node.Get("name").StringOr("")
email := node.Get("email").StringOr("")

5. Use ForEach for Performance

// ❌ Slow for large objects/arrays
for i := 0; i < array.Len(); i++ {
    item := array.Index(i)
    // Process
}

// ✅ Fast, zero-allocation
array.ArrayForEach(func(i int, item fxjson.Node) bool {
    // Process
    return true
})

JSON Serialization - Struct to JSON

FxJSON provides powerful serialization capabilities to efficiently convert Go structs, slices, maps, and other data types to JSON.

1. Basic Serialization

package main

import (
    "fmt"
    "github.com/icloudza/fxjson"
)

// Define user struct
type User struct {
    ID       int      `json:"id"`
    Name     string   `json:"name"`
    Email    string   `json:"email,omitempty"`
    IsActive bool     `json:"is_active"`
    Height   float64  `json:"height"`
    Tags     []string `json:"tags,omitempty"`
}

func main() {
    user := User{
        ID:       123,
        Name:     "John Doe",
        Email:    "john@example.com",
        IsActive: true,
        Height:   175.5,
        Tags:     []string{"developer", "golang"},
    }
    
    // Basic serialization
    jsonBytes, err := fxjson.Marshal(user)
    if err != nil {
        panic(err)
    }
    fmt.Println("Compact JSON:", string(jsonBytes))
    
    // Pretty print serialization (with indentation)
    prettyJSON, err := fxjson.MarshalIndent(user, "", "  ")
    if err != nil {
        panic(err)
    }
    fmt.Println("Pretty JSON:\n", string(prettyJSON))
    
    // Direct serialization to string
    jsonStr, err := fxjson.MarshalToString(user)
    if err != nil {
        panic(err)
    }
    fmt.Println("JSON string:", jsonStr)
}

Output:

Compact JSON: {"id":123,"name":"John Doe","email":"john@example.com","is_active":true,"height":175.5,"tags":["developer","golang"]}

Pretty JSON:
{
  "id": 123,
  "name": "John Doe", 
  "email": "john@example.com",
  "is_active": true,
  "height": 175.5,
  "tags": [
    "developer",
    "golang"
  ]
}

2. Serialization Options Configuration

// Custom serialization options
opts := fxjson.SerializeOptions{
    Indent:          "    ",  // 4-space indentation
    EscapeHTML:      true,    // Escape HTML characters
    SortKeys:        true,    // Sort keys
    OmitEmpty:       true,    // Omit empty values
    FloatPrecision:  2,       // Float precision to 2 decimal places
    UseNumberString: false,   // Don't use string for large numbers
}

// Serialize with custom options
customJSON, err := fxjson.MarshalWithOptions(user, opts)
if err != nil {
    panic(err)
}

// Preset options
// Default options (compact)
defaultJSON, _ := fxjson.MarshalWithOptions(user, fxjson.DefaultSerializeOptions)

// Pretty options (formatted)
prettyJSON, _ := fxjson.MarshalWithOptions(user, fxjson.PrettySerializeOptions)

3. High-Performance Serialization

// Ultra-fast serialization (zero allocation, no error handling)
fastJSON := fxjson.FastMarshal(user)
fmt.Println("Fast serialization:", string(fastJSON))

// Struct-specific fast serialization
structJSON, err := fxjson.MarshalStruct(user)
if err != nil {
    panic(err)
}

// Ultra-fast struct serialization (no error checking)
fastStructJSON := fxjson.MarshalStructFast(user)

4. Complex Struct Serialization

// Complex nested structure
type Company struct {
    ID          int64     `json:"id"`
    Name        string    `json:"name"`
    Employees   []User    `json:"employees"`
    Founded     time.Time `json:"founded"`
    IsPublic    bool      `json:"is_public"`
    Revenue     float64   `json:"revenue,omitempty"`
    Departments []string  `json:"departments,omitempty"`
    Metadata    map[string]interface{} `json:"metadata,omitempty"`
}

type User struct {
    ID       int      `json:"id"`
    Name     string   `json:"name"`
    Email    string   `json:"email,omitempty"`
    IsActive bool     `json:"is_active"`
    Address  *Address `json:"address,omitempty"`
    Tags     []string `json:"tags,omitempty"`
}

type Address struct {
    Street  string `json:"street"`
    City    string `json:"city"`
    Country string `json:"country"`
    ZipCode string `json:"zip_code,omitempty"`
}

func main() {
    company := Company{
        ID:   1001,
        Name: "Tech Corp",
        Employees: []User{
            {
                ID:       1,
                Name:     "John Doe",
                Email:    "john@company.com",
                IsActive: true,
                Address: &Address{
                    Street:  "123 Main St",
                    City:    "New York",
                    Country: "USA",
                    ZipCode: "10001",
                },
                Tags: []string{"backend", "go-expert"},
            },
            {
                ID:       2,
                Name:     "Jane Smith",
                Email:    "jane@company.com",
                IsActive: true,
                Tags:     []string{"frontend", "react-expert"},
            },
        },
        Founded:  time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
        IsPublic: false,
        Revenue:  5000000.50,
        Departments: []string{"Engineering", "Marketing", "HR"},
        Metadata: map[string]interface{}{
            "industry": "Software",
            "size":     "Medium",
            "locations": []string{"New York", "San Francisco", "Austin"},
        },
    }
    
    // Serialize complex structure
    companyJSON, err := fxjson.MarshalIndent(company, "", "  ")
    if err != nil {
        panic(err)
    }
    
    fmt.Println("Company JSON:")
    fmt.Println(string(companyJSON))
}

5. Batch Serialization

// Batch serialize multiple objects
users := []User{
    {ID: 1, Name: "John", Email: "john@example.com"},
    {ID: 2, Name: "Jane", Email: "jane@example.com"},
    {ID: 3, Name: "Bob", Email: "bob@example.com"},
}

// Method 1: Direct slice serialization
usersJSON, err := fxjson.Marshal(users)
if err != nil {
    panic(err)
}

// Method 2: Batch serialize to multiple JSON byte arrays
jsonArrays, err := fxjson.BatchMarshalStructs([]interface{}{
    users[0], users[1], users[2],
})
if err != nil {
    panic(err)
}

for i, jsonBytes := range jsonArrays {
    fmt.Printf("User %d: %s\n", i+1, string(jsonBytes))
}

// Method 3: Concurrent batch serialization (for large datasets)
workerCount := 4 // Use 4 worker goroutines
concurrentJSON, err := fxjson.BatchMarshalStructsConcurrent(
    []interface{}{users[0], users[1], users[2]}, 
    workerCount,
)

6. Stream Serialization

import (
    "bytes"
    "github.com/icloudza/fxjson"
)

// Stream serialize large data
func streamMarshalExample() {
    var buffer bytes.Buffer
    
    // Create stream marshaler
    marshaler := fxjson.NewStreamMarshaler(
        func(data []byte) error {
            _, err := buffer.Write(data)
            return err
        },
        fxjson.PrettySerializeOptions,
    )
    
    // Start writing array
    marshaler.StartArray()
    
    // Write users one by one
    users := []User{
        {ID: 1, Name: "John", Email: "john@example.com"},
        {ID: 2, Name: "Jane", Email: "jane@example.com"},
        {ID: 3, Name: "Bob", Email: "bob@example.com"},
    }
    
    for _, user := range users {
        err := marshaler.WriteValue(user)
        if err != nil {
            panic(err)
        }
    }
    
    // End array
    marshaler.EndArray()
    marshaler.Close()
    
    fmt.Println("Stream serialization result:")
    fmt.Println(buffer.String())
}

// Stream serialize object
func streamObjectExample() {
    var buffer bytes.Buffer
    
    marshaler := fxjson.NewStreamMarshaler(
        func(data []byte) error {
            _, err := buffer.Write(data)
            return err
        },
        fxjson.DefaultSerializeOptions,
    )
    
    // Start writing object
    marshaler.StartObject()
    
    // Write fields one by one
    marshaler.WriteField("company", "Tech Corp")
    marshaler.WriteField("employees", users)
    marshaler.WriteField("founded", time.Now())
    
    // End object
    marshaler.EndObject()
    marshaler.Close()
    
    fmt.Println("Stream object result:")
    fmt.Println(buffer.String())
}

7. Special Type Serialization

import "time"

// Time serialization
now := time.Now()

// Standard time format
timeJSON := fxjson.MarshalTime(now)
fmt.Println("Time JSON:", string(timeJSON))

// Unix timestamp
unixJSON := fxjson.MarshalTimeUnix(now)
fmt.Println("Unix timestamp:", string(unixJSON))

// Duration
duration := 5 * time.Minute
durationJSON := fxjson.MarshalDuration(duration)
fmt.Println("Duration:", string(durationJSON))

// Binary data (Base64 encoding)
binaryData := []byte("Hello, World!")
binaryJSON := fxjson.MarshalBinary(binaryData)
fmt.Println("Binary data:", string(binaryJSON))

8. Serialization Performance Optimization

// Performance comparison example
type BenchmarkData struct {
    ID    int64  `json:"id"`
    Name  string `json:"name"`
    Value string `json:"value"`
}

func performanceComparison() {
    data := BenchmarkData{
        ID:    12345,
        Name:  "Test Data",
        Value: "This is a test value",
    }
    
    // Standard serialization
    start := time.Now()
    for i := 0; i < 10000; i++ {
        _, _ = fxjson.Marshal(data)
    }
    standardTime := time.Since(start)
    
    // Fast serialization
    start = time.Now()
    for i := 0; i < 10000; i++ {
        _ = fxjson.FastMarshal(data)
    }
    fastTime := time.Since(start)
    
    // Struct-specific serialization
    start = time.Now()
    for i := 0; i < 10000; i++ {
        _ = fxjson.MarshalStructFast(data)
    }
    structTime := time.Since(start)
    
    fmt.Printf("Standard serialization: %v\n", standardTime)
    fmt.Printf("Fast serialization: %v (%.1fx faster)\n", fastTime, 
               float64(standardTime)/float64(fastTime))
    fmt.Printf("Struct serialization: %v (%.1fx faster)\n", structTime,
               float64(standardTime)/float64(structTime))
}

9. Serialization Options Details

// Complete serialization options example
func serializationOptions() {
    type Product struct {
        ID          int     `json:"id"`
        Name        string  `json:"name"`
        Price       float64 `json:"price"`
        Description string  `json:"description,omitempty"`
        HTML        string  `json:"html_content"`
        Weight      float64 `json:"weight"`
        Available   bool    `json:"available"`
    }
    
    product := Product{
        ID:          1,
        Name:        "Smartphone",
        Price:       999.99,
        Description: "", // empty value
        HTML:        "<p>This is <strong>product</strong> description</p>",
        Weight:      0.185,
        Available:   true,
    }
    
    // 1. Compact mode (default)
    compact, _ := fxjson.MarshalWithOptions(product, fxjson.SerializeOptions{
        Indent:     "",    // No indentation
        EscapeHTML: false, // Don't escape HTML
        SortKeys:   false, // Don't sort keys
        OmitEmpty:  false, // Don't omit empty values
    })
    fmt.Println("Compact mode:", string(compact))
    
    // 2. Pretty mode
    pretty, _ := fxjson.MarshalWithOptions(product, fxjson.SerializeOptions{
        Indent:     "  ",  // 2-space indentation
        EscapeHTML: false, // Don't escape HTML
        SortKeys:   true,  // Sort keys
        OmitEmpty:  true,  // Omit empty values
    })
    fmt.Println("Pretty mode:\n", string(pretty))
    
    // 3. Safe mode (for web output)
    safe, _ := fxjson.MarshalWithOptions(product, fxjson.SerializeOptions{
        Indent:          "  ",
        EscapeHTML:      true,  // Escape HTML characters
        SortKeys:        true,
        OmitEmpty:       true,
        FloatPrecision:  2,     // Keep 2 decimal places for floats
        UseNumberString: false,
    })
    fmt.Println("Safe mode:\n", string(safe))
    
    // 4. Precise numeric mode
    precise, _ := fxjson.MarshalWithOptions(product, fxjson.SerializeOptions{
        FloatPrecision:  4,    // 4 decimal places for floats
        UseNumberString: true, // Use string for large numbers
    })
    fmt.Println("Precise mode:", string(precise))
}

10. Error Handling and Debugging

// Serialization error handling
func marshalWithErrorHandling() {
    type ProblematicStruct struct {
        Channel chan int `json:"channel"` // Cannot serialize
        Func    func()   `json:"func"`    // Cannot serialize
        Valid   string   `json:"valid"`
    }
    
    data := ProblematicStruct{
        Channel: make(chan int),
        Func:    func() {},
        Valid:   "This field can be serialized",
    }
    
    // Try serialization
    result, err := fxjson.Marshal(data)
    if err != nil {
        fmt.Printf("Serialization error: %v\n", err)
        
        // Use FastMarshal as fallback (ignores errors)
        safeResult := fxjson.FastMarshal(struct {
            Valid string `json:"valid"`
        }{
            Valid: data.Valid,
        })
        fmt.Printf("Safe serialization result: %s\n", string(safeResult))
    } else {
        fmt.Printf("Serialization successful: %s\n", string(result))
    }
}

// Debug serialization process
func debugMarshaling() {
    user := User{
        ID:   123,
        Name: "Debug User",
    }
    
    // Enable debug mode
    fxjson.EnableDebugMode()
    defer fxjson.DisableDebugMode()
    
    // Serialize and observe debug info
    result, err := fxjson.Marshal(user)
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("Debug serialization result: %s\n", string(result))
}

11. Serialization Best Practices

// ✅ Choose the right serialization method
// Normal scenarios - use Marshal
data, err := fxjson.Marshal(obj)

// Performance-sensitive scenarios - use FastMarshal  
data := fxjson.FastMarshal(obj)

// Large datasets - use batch or stream serialization
results, err := fxjson.BatchMarshalStructs(objects)

// ✅ Use appropriate options
opts := fxjson.SerializeOptions{
    Indent:     "  ",   // Pretty print for development
    OmitEmpty:  true,   // Reduce output size
    SortKeys:   true,   // Ensure consistency
    EscapeHTML: true,   // Web safety
}

// ✅ Cache frequently used structures
type CachedStruct struct {
    cachedJSON []byte
    dirty      bool
}

func (c *CachedStruct) MarshalJSON() ([]byte, error) {
    if c.dirty || c.cachedJSON == nil {
        var err error
        c.cachedJSON, err = fxjson.Marshal(c)
        if err != nil {
            return nil, err
        }
        c.dirty = false
    }
    return c.cachedJSON, nil
}

Clone this wiki locally