Skip to content

FxJSON use guidelines (English)

CloudZA edited this page Aug 16, 2025 · 3 revisions

🚀 FxJSON Complete Guide (English) | 中文 Chinese

Table of Contents

What is FxJSON?

FxJSON is a high-performance JSON parsing library for Go. Think of it as a turbo-charged version of the standard JSON library - it's designed to be fast, memory-efficient, and easy to use.

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
})

Clone this wiki locally