Skip to content

FxJSON use guidelines (English)

CloudZA edited this page Sep 4, 2025 · 3 revisions

FxJSON Wiki - Complete Guide for Beginners & Experts

Table of Contents

  1. Quick Start
  2. Core Concepts
  3. Basic Operations
  4. Advanced Features
  5. JSON Serialization
  6. Performance Tips
  7. Real-world Examples
  8. Best Practices
  9. Troubleshooting

Quick Start

FxJSON is a high-performance JSON parsing library for Go that makes JSON processing fast, safe, and easy. Think of it as the standard JSON library, but supercharged!

Why Choose FxJSON?

Before (Standard library):

// Lots of error handling, memory allocations
var data map[string]interface{}
if err := json.Unmarshal(jsonBytes, &data); err != nil {
    // Handle error
}
name, ok := data["name"].(string)
if !ok {
    name = "Unknown"
}

After (FxJSON):

// Simple, fast, safe
node := fxjson.FromBytes(jsonBytes)
name := node.Get("name").StringOr("Unknown")  // One line, no errors!

Performance at a Glance

  • 67x faster array processing
  • 20x faster object traversal
  • Zero memory allocations for basic operations
  • Built-in safety with default values

Installation

go get github.com/icloudza/fxjson

Your First FxJSON Program

package main

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

func main() {
    // Step 1: Parse JSON from bytes
    jsonData := []byte(`{
        "name": "Alice",
        "age": 30,
        "active": true
    }`)
    
    // Step 2: Create a node from JSON
    node := fxjson.FromBytes(jsonData)
    
    // Step 3: Extract values safely with defaults
    name := node.Get("name").StringOr("Unknown")
    age := node.Get("age").IntOr(0)
    active := node.Get("active").BoolOr(false)
    
    fmt.Printf("%s is %d years old (active: %v)\n", name, age, active)
    // Output: Alice is 30 years old (active: true)
}

Key Benefits You Just Experienced

  1. No error handling needed - StringOr(), IntOr(), BoolOr() provide safe defaults
  2. Type safety - Automatic conversion with fallbacks
  3. Clean code - Simple, readable API
  4. High performance - Faster than standard library

Core Concepts

Understanding the Node

A Node is FxJSON's main type - think of it as a smart wrapper around any JSON value. Whether you're dealing with an object, array, string, number, boolean, or null, it's all represented as a Node.

jsonData := []byte(`{
    "name": "Alice",
    "age": 30,
    "hobbies": ["reading", "coding"],
    "address": {"city": "New York"}
}`)

root := fxjson.FromBytes(jsonData)

// Every field access returns a Node
nameNode := root.Get("name")      // Node containing "Alice"
ageNode := root.Get("age")        // Node containing 30
hobbiesNode := root.Get("hobbies") // Node containing ["reading", "coding"]

Safe Value Extraction (The FxJSON Way)

The Problem with Traditional JSON:

// Traditional way - lots of error checking
var data map[string]interface{}
json.Unmarshal(jsonBytes, &data)
name, ok := data["name"].(string)
if !ok {
    name = "default"
}

The FxJSON Solution:

// FxJSON way - simple and safe
node := fxjson.FromBytes(jsonBytes)
name := node.Get("name").StringOr("default")  // One line, no errors!

Path Navigation Made Easy

For nested JSON, use dot notation paths:

// Instead of chaining multiple Get() calls
city := root.Get("address").Get("city").StringOr("")

// Use paths for cleaner code
city := root.GetPath("address.city").StringOr("")

// Works with arrays too
jsonData := []byte(`{"users": [{"name": "Alice"}, {"name": "Bob"}]}`)
node := fxjson.FromBytes(jsonData)
secondUser := node.GetPath("users[1].name").StringOr("") // "Bob"

Basic Operations

Working with JSON Objects

JSON objects are like Go maps - collections of key-value pairs:

// Example user data
userJSON := []byte(`{
    "id": 12345,
    "name": "Alice",
    "email": "alice@example.com",
    "active": true,
    "profile": {
        "city": "New York",
        "age": 28
    }
}`)

user := fxjson.FromBytes(userJSON)

// Access basic fields
id := user.Get("id").IntOr(0)
name := user.Get("name").StringOr("Unknown")
email := user.Get("email").StringOr("")
active := user.Get("active").BoolOr(false)

// Access nested fields (two ways)
city1 := user.Get("profile").Get("city").StringOr("")  // Method 1
city2 := user.GetPath("profile.city").StringOr("")     // Method 2 (cleaner)

// Check if a field exists
if user.HasKey("email") {
    fmt.Println("User has an email address")
}

// Get all keys from an object
keys := user.GetAllKeys()
fmt.Println("User fields:", keys) // [id, name, email, active, profile]

Working with JSON Arrays

JSON arrays are ordered lists of values:

// Example shopping cart
cartJSON := []byte(`{
    "items": [
        {"name": "Laptop", "price": 999},
        {"name": "Mouse", "price": 29},
        {"name": "Keyboard", "price": 79}
    ],
    "tags": ["electronics", "computers"]
}`)

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

// Get array length
count := items.Len()
fmt.Printf("Cart has %d items\n", count) // Cart has 3 items

// Access array elements by index
firstItem := items.Index(0)  // Get first item
firstItemName := firstItem.Get("name").StringOr("")
firstItemPrice := firstItem.Get("price").IntOr(0)

// Iterate through array (high-performance way)
var total int
items.ArrayForEach(func(index int, item fxjson.Node) bool {
    name := item.Get("name").StringOr("")
    price := item.Get("price").IntOr(0)
    total += price
    
    fmt.Printf("%d. %s: $%d\n", index+1, name, price)
    return true // continue to next item
})

fmt.Printf("Total: $%d\n", total)

Array Helper Functions

tags := cart.Get("tags")

// Get first and last elements
first := tags.First().StringOr("")  // "electronics"
last := tags.Last().StringOr("")    // "computers"

// Convert to Go slice for advanced operations
if tagList, err := tags.ToStringSlice(); err == nil {
    for i, tag := range tagList {
        fmt.Printf("Tag %d: %s\n", i+1, tag)
    }
}

Type Checking and Safe Conversion

FxJSON automatically handles type checking and conversion:

// Mixed types in JSON
mixedJSON := []byte(`{
    "name": "Alice",
    "age": 30,
    "height": 5.6,
    "active": true,
    "skills": ["Go", "JavaScript"],
    "address": {"city": "NYC"}
}`)

data := fxjson.FromBytes(mixedJSON)

// Safe conversion with defaults (recommended)
name := data.Get("name").StringOr("Unknown")     // String
age := data.Get("age").IntOr(0)                  // Integer
height := data.Get("height").FloatOr(0.0)       // Float
active := data.Get("active").BoolOr(false)      // Boolean
skills := data.Get("skills")                     // Array
address := data.Get("address")                  // Object

// Check types before processing (optional)
if skills.IsArray() {
    fmt.Printf("User has %d skills\n", skills.Len())
}

if address.IsObject() {
    city := address.Get("city").StringOr("Unknown")
    fmt.Printf("User lives in %s\n", city)
}

The Power of Default Values

One of FxJSON's biggest advantages is safe extraction with defaults:

// These will NEVER panic or cause errors
unknownField := data.Get("nonexistent")
safeString := unknownField.StringOr("default")  // "default"
safeNumber := unknownField.IntOr(-1)           // -1
safeBool := unknownField.BoolOr(false)         // false

fmt.Printf("Safe values: %s, %d, %v\n", safeString, safeNumber, safeBool)

Advanced Features

Built-in Data Validation

FxJSON includes common validators to make data validation easy:

// User registration form data
formData := []byte(`{
    "email": "user@example.com",
    "website": "https://mysite.com",
    "phone": "+1-555-0123",
    "id": "123e4567-e89b-12d3-a456-426614174000"
}`)

form := fxjson.FromBytes(formData)

// Validate email
if form.Get("email").IsValidEmail() {
    fmt.Println("✅ Email is valid")
}

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

// Validate phone number
if form.Get("phone").IsValidPhone() {
    fmt.Println("✅ Phone number is valid")
}

// Validate UUID
if form.Get("id").IsValidUUID() {
    fmt.Println("✅ ID is a valid UUID")
}

String Operations

userData := []byte(`{
    "name": "  John Doe  ",
    "bio": "Software Developer",
    "website": "https://johndoe.dev"
}`)

user := fxjson.FromBytes(userData)

name := user.Get("name")
// Built-in string operations
trimmed, _ := name.Trim()           // "John Doe" (no spaces)
upper, _ := name.ToUpper()          // "  JOHN DOE  "
lower, _ := name.ToLower()          // "  john doe  "

website := user.Get("website")
// String checking
if website.StartsWith("https://") {
    fmt.Println("✅ Secure website")
}

if website.Contains("johndoe") {
    fmt.Println("✅ Personal website")
}

Batch Operations

Get multiple values efficiently:

orderJSON := []byte(`{
    "id": "ORDER-123",
    "customer": {"name": "Alice", "email": "alice@example.com"},
    "total": 99.99
}`)

order := fxjson.FromBytes(orderJSON)

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

orderId := values[0].StringOr("")
customerName := values[1].StringOr("Unknown")
email := values[2].StringOr("")
total := values[3].FloatOr(0.0)

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

// Check if all required fields exist
required := []string{"id", "customer.name", "total"}
if order.HasAllPaths(required...) {
    fmt.Println("✅ All required fields present")
}

Searching and Filtering Arrays

Find and filter array elements:

productsJSON := []byte(`{
    "products": [
        {"id": 1, "name": "Laptop", "price": 999, "inStock": true},
        {"id": 2, "name": "Mouse", "price": 29, "inStock": false},
        {"id": 3, "name": "Keyboard", "price": 79, "inStock": true}
    ]
}`)

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

// Find first product in stock
_, firstInStock, found := products.FindInArray(func(idx int, product fxjson.Node) bool {
    return product.Get("inStock").BoolOr(false)
})

if found {
    name := firstInStock.Get("name").StringOr("")
    fmt.Printf("First in-stock item: %s\n", name)
}

// Filter all products under $100
affordable := products.FilterArray(func(idx int, product fxjson.Node) bool {
    return product.Get("price").IntOr(0) < 100
})

fmt.Printf("Found %d affordable products\n", len(affordable))

JSON Serialization

Convert Go structs to JSON with high performance:

Basic Serialization

package main

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

// Define a user struct
type User struct {
    ID       int      `json:"id"`
    Name     string   `json:"name"`
    Email    string   `json:"email,omitempty"`
    Active   bool     `json:"active"`
    Tags     []string `json:"tags,omitempty"`
}

func main() {
    user := User{
        ID:     123,
        Name:   "Alice",
        Email:  "alice@example.com",
        Active: true,
        Tags:   []string{"developer", "golang"},
    }
    
    // Basic serialization (compact)
    jsonBytes, err := fxjson.Marshal(user)
    if err != nil {
        panic(err)
    }
    fmt.Println("Compact:", string(jsonBytes))
    
    // Pretty print serialization
    prettyJSON, err := fxjson.MarshalIndent(user, "", "  ")
    if err != nil {
        panic(err)
    }
    fmt.Println("Pretty:\n", string(prettyJSON))
}

Serialization Options

// Custom serialization options
opts := fxjson.SerializeOptions{
    Indent:         "  ",   // 2-space indentation
    EscapeHTML:     true,   // Escape HTML characters
    SortKeys:       true,   // Sort object keys
    OmitEmpty:      true,   // Skip empty fields
    FloatPrecision: 2,      // 2 decimal places for floats
}

customJSON, err := fxjson.MarshalWithOptions(user, opts)
if err != nil {
    panic(err)
}

// Or use presets
prettyJSON, _ := fxjson.MarshalWithOptions(user, fxjson.PrettySerializeOptions)
compactJSON, _ := fxjson.MarshalWithOptions(user, fxjson.DefaultSerializeOptions)

High-Performance Serialization

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

// Batch serialize multiple objects
users := []User{user1, user2, user3}
batchJSON, err := fxjson.BatchMarshalStructs([]interface{}{user1, user2, user3})

Performance Tips

Use ArrayForEach for Large Arrays

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

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

Use Default Values Instead of Error Checking

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

// ✅ FxJSON way with defaults
value := node.Get("key").StringOr("default")

Use Paths 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("")

Enable Caching for Repeated Parsing

// For frequently parsed JSON, use caching
cache := fxjson.NewMemoryCache(100)
fxjson.EnableCaching(cache)

// Parse with cache (subsequent parses will be faster)
node := fxjson.FromBytesWithCache(jsonData, 5*time.Minute)

Real-world Examples

REST API Response Handler

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

Configuration File Manager

type Config struct {
    Server struct {
        Host string `json:"host"`
        Port int    `json:"port"`
    } `json:"server"`
    Database struct {
        URL      string `json:"url"`
        PoolSize int    `json:"poolSize"`
    } `json:"database"`
}

func loadConfig(env string) (*Config, error) {
    // Load base config
    baseConfig, _ := os.ReadFile("config/base.json")
    
    // Load environment-specific config
    envConfig, _ := os.ReadFile(fmt.Sprintf("config/%s.json", env))
    if envConfig == nil {
        envConfig = []byte("{}")
    }
    
    // Parse and merge
    base := fxjson.FromBytes(baseConfig)
    envSpecific := fxjson.FromBytes(envConfig)
    final := base.Merge(envSpecific)
    
    // Validate required fields
    required := []string{"server.host", "server.port", "database.url"}
    if !final.HasAllPaths(required...) {
        return nil, errors.New("missing required configuration")
    }
    
    // Decode to struct
    var config Config
    if err := final.Decode(&config); err != nil {
        return nil, err
    }
    
    // Apply defaults
    if config.Database.PoolSize == 0 {
        config.Database.PoolSize = 10
    }
    
    return &config, nil
}

Log Analyzer

func analyzeJSONLogs(logData []byte) {
    lines := strings.Split(string(logData), "\n")
    
    var errorCount, warnCount, infoCount int
    errorMessages := make(map[string]int)
    
    for _, line := range lines {
        if line == "" {
            continue
        }
        
        logEntry := fxjson.FromBytes([]byte(line))
        
        level := logEntry.Get("level").StringOr("")
        switch level {
        case "ERROR":
            errorCount++
            msg := logEntry.Get("message").StringOr("")
            errorMessages[msg]++
        case "WARN":
            warnCount++
        case "INFO":
            infoCount++
        }
        
        // Check for server errors
        if logEntry.Get("status").IntOr(0) >= 500 {
            timestamp := logEntry.Get("timestamp").StringOr("")
            message := logEntry.Get("message").StringOr("")
            fmt.Printf("Server error at %s: %s\n", timestamp, message)
        }
    }
    
    fmt.Printf("Log Summary: %d errors, %d warnings, %d info\n", 
               errorCount, warnCount, infoCount)
    
    // Show frequent errors
    for msg, count := range errorMessages {
        if count > 1 {
            fmt.Printf("Frequent error: %s (%d times)\n", msg, count)
        }
    }
}

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 When Needed

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

4. Validate Before Processing

// ✅ Always validate structure first
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
})

6. Choose the Right Serialization Method

// Normal scenarios - use Marshal
data, err := fxjson.Marshal(obj)

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

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

Troubleshooting

Common Issues and Solutions

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

Number Precision

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

// Solution: 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

Large Arrays

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

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

items.ArrayForEach(func(i int, item fxjson.Node) bool {
    // Process item efficiently
    return true
})

Debug Tips

  1. Check node types before conversion: node.IsString(), node.IsArray(), etc.
  2. Use HasKey() to verify field existence before accessing
  3. Enable caching for frequently parsed JSON to improve performance
  4. Use paths (GetPath()) for deep nested structures

FxJSON makes JSON processing fast, safe, and fun! 🚀

For more examples and advanced usage, check the project repository.

Clone this wiki locally