json

package
v1.2.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 1, 2026 License: Apache-2.0 Imports: 2 Imported by: 1

README

JSON

Utilities for converting between JSON and Go structs.

Overview

The json package provides convenient wrapper functions around Go's encoding/json package, simplifying common JSON operations like marshaling, unmarshaling, and file I/O with type-safe generic functions.

Features

  • Struct to JSON - Convert Go structs to JSON strings
  • Formatted Output - Generate indented, human-readable JSON
  • Generic File Reading - Type-safe JSON file deserialization
  • Generic String Parsing - Type-safe JSON string deserialization
  • Simple API - Minimal wrapper around standard library

Installation

go get -u github.com/common-library/go/json

Quick Start

Marshal to JSON
import "github.com/common-library/go/json"

type User struct {
    Name  string `json:"name"`
    Email string `json:"email"`
    Age   int    `json:"age"`
}

user := User{
    Name:  "Alice",
    Email: "alice@example.com",
    Age:   30,
}

// Compact JSON
jsonStr, err := json.ToString(user)
// Output: {"name":"Alice","email":"alice@example.com","age":30}

// Formatted JSON
jsonStr, err := json.ToStringIndent(user, "", "  ")
// Output:
// {
//   "name": "Alice",
//   "email": "alice@example.com",
//   "age": 30
// }
Unmarshal from JSON
// From string
jsonData := `{"name":"Bob","email":"bob@example.com","age":25}`
user, err := json.ConvertFromString[User](jsonData)

// From file
user, err := json.ConvertFromFile[User]("user.json")

API Reference

ToString
func ToString(input any) (string, error)

Converts a Go value to a compact JSON string.

Parameters:

  • input - Any Go value to convert (typically a struct)

Returns:

  • string - Compact JSON representation
  • error - Error if marshaling fails

Behavior:

  • Uses json.Marshal internally
  • Produces compact output with no whitespace
  • Exported struct fields only
  • Respects json struct tags
ToStringIndent
func ToStringIndent(input any, prefix string, indent string) (string, error)

Converts a Go value to a formatted JSON string with custom indentation.

Parameters:

  • input - Any Go value to convert
  • prefix - String prepended to each line (typically "")
  • indent - String used for each indentation level (e.g., "\t" or " ")

Returns:

  • string - Formatted JSON representation
  • error - Error if marshaling fails

Behavior:

  • Uses json.MarshalIndent internally
  • Produces human-readable output
  • Adds prefix to beginning of each line
  • Adds indent for each nesting level
ConvertFromFile
func ConvertFromFile[T any](fileName string) (T, error)

Reads a JSON file and converts it to type T.

Type Parameters:

  • T - Target type (struct, slice, map, or primitive)

Parameters:

  • fileName - Path to JSON file

Returns:

  • T - Unmarshaled data as type T
  • error - Error if file reading or unmarshaling fails

Behavior:

  • Reads entire file into memory
  • Unmarshals JSON to specified type
  • Type-safe with compile-time checking
ConvertFromString
func ConvertFromString[T any](data string) (T, error)

Converts a JSON string to type T.

Type Parameters:

  • T - Target type (struct, slice, map, or primitive)

Parameters:

  • data - JSON string to parse

Returns:

  • T - Unmarshaled data as type T
  • error - Error if unmarshaling fails

Behavior:

  • Uses json.Unmarshal internally
  • Type-safe with compile-time checking
  • Handles nested structures

Complete Examples

Struct to JSON
package main

import (
    "fmt"
    "log"
    
    "github.com/common-library/go/json"
)

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

type User struct {
    ID      int     `json:"id"`
    Name    string  `json:"name"`
    Email   string  `json:"email"`
    Age     int     `json:"age"`
    Address Address `json:"address"`
}

func main() {
    user := User{
        ID:    1,
        Name:  "Alice Johnson",
        Email: "alice@example.com",
        Age:   30,
        Address: Address{
            Street:  "123 Main St",
            City:    "New York",
            Country: "USA",
            ZipCode: "10001",
        },
    }
    
    // Compact JSON
    compact, err := json.ToString(user)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Compact JSON:")
    fmt.Println(compact)
    
    // Formatted JSON
    formatted, err := json.ToStringIndent(user, "", "  ")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("\nFormatted JSON:")
    fmt.Println(formatted)
}
JSON to Struct
package main

import (
    "fmt"
    "log"
    
    "github.com/common-library/go/json"
)

type Config struct {
    Host     string `json:"host"`
    Port     int    `json:"port"`
    Database string `json:"database"`
    Username string `json:"username"`
    Password string `json:"password"`
}

func main() {
    // From string
    jsonData := `{
        "host": "localhost",
        "port": 5432,
        "database": "myapp",
        "username": "admin",
        "password": "secret"
    }`
    
    config, err := json.ConvertFromString[Config](jsonData)
    if err != nil {
        log.Fatal(err)
    }
    
    fmt.Printf("Config: %+v\n", config)
}
Read JSON File
package main

import (
    "fmt"
    "log"
    
    "github.com/common-library/go/json"
)

type User struct {
    Name  string `json:"name"`
    Email string `json:"email"`
    Age   int    `json:"age"`
}

func main() {
    // users.json contains:
    // [
    //   {"name": "Alice", "email": "alice@example.com", "age": 30},
    //   {"name": "Bob", "email": "bob@example.com", "age": 25}
    // ]
    
    users, err := json.ConvertFromFile[[]User]("users.json")
    if err != nil {
        log.Fatal(err)
    }
    
    fmt.Printf("Loaded %d users:\n", len(users))
    for _, user := range users {
        fmt.Printf("  %s (%s) - %d years old\n", user.Name, user.Email, user.Age)
    }
}
Write JSON File
package main

import (
    "log"
    
    "github.com/common-library/go/file"
    "github.com/common-library/go/json"
)

type Settings struct {
    Theme      string `json:"theme"`
    Language   string `json:"language"`
    AutoSave   bool   `json:"auto_save"`
    MaxBackups int    `json:"max_backups"`
}

func main() {
    settings := Settings{
        Theme:      "dark",
        Language:   "en",
        AutoSave:   true,
        MaxBackups: 5,
    }
    
    // Convert to formatted JSON
    jsonData, err := json.ToStringIndent(settings, "", "  ")
    if err != nil {
        log.Fatal(err)
    }
    
    // Write to file
    err = file.Write("settings.json", jsonData)
    if err != nil {
        log.Fatal(err)
    }
    
    log.Println("Settings saved to settings.json")
}
Working with Maps
package main

import (
    "fmt"
    "log"
    
    "github.com/common-library/go/json"
)

func main() {
    // Marshal map to JSON
    data := map[string]interface{}{
        "name": "Alice",
        "age":  30,
        "active": true,
        "tags": []string{"admin", "developer"},
    }
    
    jsonStr, err := json.ToStringIndent(data, "", "  ")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Map as JSON:")
    fmt.Println(jsonStr)
    
    // Unmarshal JSON to map
    jsonData := `{
        "server": "localhost",
        "port": 8080,
        "debug": true
    }`
    
    config, err := json.ConvertFromString[map[string]interface{}](jsonData)
    if err != nil {
        log.Fatal(err)
    }
    
    fmt.Printf("\nParsed config: %+v\n", config)
    fmt.Printf("Server: %s\n", config["server"])
    fmt.Printf("Port: %v\n", config["port"])
    fmt.Printf("Debug: %v\n", config["debug"])
}
Slices and Arrays
package main

import (
    "fmt"
    "log"
    
    "github.com/common-library/go/json"
)

type Product struct {
    ID    int     `json:"id"`
    Name  string  `json:"name"`
    Price float64 `json:"price"`
}

func main() {
    // Marshal slice to JSON
    products := []Product{
        {ID: 1, Name: "Laptop", Price: 999.99},
        {ID: 2, Name: "Mouse", Price: 29.99},
        {ID: 3, Name: "Keyboard", Price: 79.99},
    }
    
    jsonStr, err := json.ToStringIndent(products, "", "  ")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Products as JSON:")
    fmt.Println(jsonStr)
    
    // Unmarshal JSON array
    jsonData := `[
        {"id": 10, "name": "Monitor", "price": 299.99},
        {"id": 11, "name": "Webcam", "price": 89.99}
    ]`
    
    newProducts, err := json.ConvertFromString[[]Product](jsonData)
    if err != nil {
        log.Fatal(err)
    }
    
    fmt.Printf("\nLoaded %d products:\n", len(newProducts))
    for _, p := range newProducts {
        fmt.Printf("  %s: $%.2f\n", p.Name, p.Price)
    }
}
Nested Structures
package main

import (
    "fmt"
    "log"
    
    "github.com/common-library/go/json"
)

type Company struct {
    Name      string       `json:"name"`
    Founded   int          `json:"founded"`
    Employees []Employee   `json:"employees"`
    Addresses []Address    `json:"addresses"`
}

type Employee struct {
    Name     string `json:"name"`
    Position string `json:"position"`
    Salary   int    `json:"salary"`
}

type Address struct {
    Type    string `json:"type"` // e.g., "headquarters", "branch"
    Street  string `json:"street"`
    City    string `json:"city"`
    Country string `json:"country"`
}

func main() {
    company := Company{
        Name:    "Tech Corp",
        Founded: 2010,
        Employees: []Employee{
            {Name: "Alice", Position: "CEO", Salary: 200000},
            {Name: "Bob", Position: "CTO", Salary: 180000},
        },
        Addresses: []Address{
            {Type: "headquarters", Street: "100 Tech Blvd", City: "San Francisco", Country: "USA"},
            {Type: "branch", Street: "200 Innovation Way", City: "Austin", Country: "USA"},
        },
    }
    
    // Convert to JSON
    jsonStr, err := json.ToStringIndent(company, "", "  ")
    if err != nil {
        log.Fatal(err)
    }
    
    fmt.Println("Company as JSON:")
    fmt.Println(jsonStr)
    
    // Parse back
    parsed, err := json.ConvertFromString[Company](jsonStr)
    if err != nil {
        log.Fatal(err)
    }
    
    fmt.Printf("\nParsed: %s with %d employees\n", parsed.Name, len(parsed.Employees))
}
API Response Handling
package main

import (
    "fmt"
    "log"
    
    "github.com/common-library/go/json"
)

type APIResponse struct {
    Success bool        `json:"success"`
    Message string      `json:"message"`
    Data    interface{} `json:"data"`
}

type UserData struct {
    ID       int      `json:"id"`
    Username string   `json:"username"`
    Roles    []string `json:"roles"`
}

func main() {
    // Parse API response
    responseJSON := `{
        "success": true,
        "message": "User retrieved successfully",
        "data": {
            "id": 123,
            "username": "alice",
            "roles": ["admin", "user"]
        }
    }`
    
    response, err := json.ConvertFromString[APIResponse](responseJSON)
    if err != nil {
        log.Fatal(err)
    }
    
    if response.Success {
        fmt.Printf("Message: %s\n", response.Message)
        
        // Convert data field to specific type
        dataJSON, _ := json.ToString(response.Data)
        userData, err := json.ConvertFromString[UserData](dataJSON)
        if err != nil {
            log.Fatal(err)
        }
        
        fmt.Printf("User: %s (ID: %d)\n", userData.Username, userData.ID)
        fmt.Printf("Roles: %v\n", userData.Roles)
    }
}

Best Practices

1. Use Struct Tags
// Good: Use struct tags to control JSON field names
type User struct {
    ID        int    `json:"id"`
    FirstName string `json:"first_name"`
    LastName  string `json:"last_name"`
    Email     string `json:"email"`
}

// Avoid: Relying on default field names
type User struct {
    ID        int    // Becomes "ID" in JSON
    FirstName string // Becomes "FirstName" in JSON
}
2. Handle Errors Properly
// Good: Always check errors
jsonStr, err := json.ToString(data)
if err != nil {
    log.Printf("Failed to marshal data: %v", err)
    return err
}

// Avoid: Ignoring errors
jsonStr, _ := json.ToString(data)
3. Use Type Parameters Correctly
// Good: Explicit type parameter
users, err := json.ConvertFromFile[[]User]("users.json")

config, err := json.ConvertFromString[Config](jsonData)

// Avoid: Wrong type parameter
// users, err := json.ConvertFromFile[User]("users.json")
// Will fail if file contains array
4. Validate JSON Data
// Good: Validate after unmarshaling
user, err := json.ConvertFromString[User](jsonData)
if err != nil {
    return fmt.Errorf("invalid JSON: %w", err)
}

if user.Email == "" {
    return fmt.Errorf("email is required")
}

if user.Age < 0 {
    return fmt.Errorf("invalid age: %d", user.Age)
}
5. Use Appropriate Indentation
// Good: Use 2 or 4 spaces for readability
jsonStr, _ := json.ToStringIndent(data, "", "  ")

// Good: Use tabs for compact formatting
jsonStr, _ := json.ToStringIndent(data, "", "\t")

// Avoid: Inconsistent indentation
jsonStr, _ := json.ToStringIndent(data, "", "   ") // 3 spaces

Common Use Cases

Configuration Files
type Config struct {
    Database DatabaseConfig `json:"database"`
    Server   ServerConfig   `json:"server"`
    Logging  LogConfig      `json:"logging"`
}

// Load config
config, err := json.ConvertFromFile[Config]("config.json")

// Save config
jsonData, _ := json.ToStringIndent(config, "", "  ")
file.Write("config.json", jsonData)
API Communication
// Request payload
type CreateUserRequest struct {
    Name  string `json:"name"`
    Email string `json:"email"`
}

request := CreateUserRequest{Name: "Alice", Email: "alice@example.com"}
jsonBody, _ := json.ToString(request)

// Send HTTP request with jsonBody
// ...

// Parse response
type CreateUserResponse struct {
    ID      int    `json:"id"`
    Message string `json:"message"`
}

response, err := json.ConvertFromString[CreateUserResponse](responseBody)
Data Export
// Export data to JSON file
type Report struct {
    Title     string    `json:"title"`
    Generated time.Time `json:"generated"`
    Data      []Record  `json:"data"`
}

report := Report{
    Title:     "Monthly Report",
    Generated: time.Now(),
    Data:      records,
}

jsonData, _ := json.ToStringIndent(report, "", "  ")
file.Write("report.json", jsonData)

Error Handling

Marshaling Errors
jsonStr, err := json.ToString(data)
if err != nil {
    // Possible errors:
    // - Unsupported type (e.g., channel, function)
    // - Circular reference
    // - Invalid UTF-8
    log.Printf("Marshal error: %v", err)
}
Unmarshaling Errors
user, err := json.ConvertFromString[User](jsonData)
if err != nil {
    // Possible errors:
    // - Invalid JSON syntax
    // - Type mismatch (e.g., string where number expected)
    // - Out of range values
    log.Printf("Unmarshal error: %v", err)
}
File Errors
config, err := json.ConvertFromFile[Config]("config.json")
if err != nil {
    // Possible errors:
    // - File not found
    // - Permission denied
    // - Invalid JSON in file
    log.Printf("File error: %v", err)
}

Performance Tips

  1. Reuse Buffers - For frequent marshaling, consider using encoding/json directly with buffer reuse
  2. Streaming - For large files, use json.Decoder/Encoder for streaming instead of loading entire file
  3. Avoid String Conversion - When working with bytes, use encoding/json directly to avoid string conversions
  4. Struct Tags - Use omitempty to reduce output size: json:"field,omitempty"

Testing

Unit Test Example
func TestUserSerialization(t *testing.T) {
    user := User{
        ID:    1,
        Name:  "Alice",
        Email: "alice@example.com",
    }
    
    // Marshal
    jsonStr, err := json.ToString(user)
    if err != nil {
        t.Fatalf("Marshal failed: %v", err)
    }
    
    // Unmarshal
    parsed, err := json.ConvertFromString[User](jsonStr)
    if err != nil {
        t.Fatalf("Unmarshal failed: %v", err)
    }
    
    // Verify
    if parsed.ID != user.ID {
        t.Errorf("Expected ID %d, got %d", user.ID, parsed.ID)
    }
    
    if parsed.Name != user.Name {
        t.Errorf("Expected name %s, got %s", user.Name, parsed.Name)
    }
}

Dependencies

  • encoding/json - Go standard library
  • github.com/common-library/go/file - File operations

Further Reading

Documentation

Overview

Package json provides utilities for converting between JSON and Go structs.

This package simplifies JSON marshaling and unmarshaling operations with convenient wrapper functions around Go's encoding/json package.

Features:

  • Struct to JSON string conversion
  • Formatted JSON output with indentation
  • Generic type conversion from JSON files
  • Generic type conversion from JSON strings

Example:

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

user := User{Name: "Alice", Age: 30}
jsonStr, _ := json.ToString(user)

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ConvertFromFile

func ConvertFromFile[T any](fileName string) (T, error)

ConvertFromFile reads a JSON file and converts it to the specified type T.

This generic function reads JSON data from a file and unmarshals it into the specified Go type. The type parameter T must be provided explicitly at the call site.

Parameters:

  • fileName: Path to the JSON file to read

Returns:

  • T: The unmarshaled data as type T
  • error: Error if file reading fails or JSON unmarshaling fails

The function performs two operations:

  1. Reads the file contents using file.Read
  2. Unmarshals the JSON data into type T

Type parameter T can be any valid Go type including structs, slices, maps, or primitive types.

Example with struct:

type Config struct {
    Host     string `json:"host"`
    Port     int    `json:"port"`
    Database string `json:"database"`
}

// config.json contains:
// {
//   "host": "localhost",
//   "port": 5432,
//   "database": "mydb"
// }

config, err := json.ConvertFromFile[Config]("config.json")
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Host: %s, Port: %d\n", config.Host, config.Port)

Example with slice:

type User struct {
    Name  string `json:"name"`
    Email string `json:"email"`
}

// users.json contains:
// [
//   {"name": "Alice", "email": "alice@example.com"},
//   {"name": "Bob", "email": "bob@example.com"}
// ]

users, err := json.ConvertFromFile[[]User]("users.json")
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Loaded %d users\n", len(users))

Example with map:

settings, err := json.ConvertFromFile[map[string]interface{}]("settings.json")
// Unmarshals JSON object to map[string]interface{}

func ConvertFromString

func ConvertFromString[T any](data string) (T, error)

ConvertFromString converts a JSON string to the specified type T.

This generic function unmarshals JSON data from a string into the specified Go type. The type parameter T must be provided explicitly at the call site.

Parameters:

  • data: JSON string to parse

Returns:

  • T: The unmarshaled data as type T
  • error: Error if JSON parsing or unmarshaling fails

The function uses json.Unmarshal internally, so the same rules apply:

  • JSON field names are matched to struct field names (case-insensitive)
  • Struct tags control how JSON fields map to struct fields
  • Extra JSON fields are ignored
  • Missing JSON fields result in zero values

Example with struct:

type User struct {
    Name  string `json:"name"`
    Email string `json:"email"`
    Age   int    `json:"age"`
}

jsonData := `{
    "name": "Alice",
    "email": "alice@example.com",
    "age": 30
}`

user, err := json.ConvertFromString[User](jsonData)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("User: %+v\n", user)
// Output: User: {Name:Alice Email:alice@example.com Age:30}

Example with slice:

jsonData := `[
    {"name": "Alice", "age": 30},
    {"name": "Bob", "age": 25}
]`

users, err := json.ConvertFromString[[]User](jsonData)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Loaded %d users\n", len(users))

Example with map:

jsonData := `{"key1": "value1", "key2": "value2"}`

data, err := json.ConvertFromString[map[string]string](jsonData)
// Unmarshals to map[string]string

Example with primitive type:

jsonData := `[1, 2, 3, 4, 5]`

numbers, err := json.ConvertFromString[[]int](jsonData)
// Unmarshals to []int

func ToString

func ToString(input any) (string, error)

ToString converts a Go struct to a JSON string.

This function marshals the input struct into compact JSON format without any formatting or indentation.

Parameters:

  • input: Any Go value to convert to JSON (typically a struct)

Returns:

  • string: JSON representation of the input
  • error: Error if marshaling fails (e.g., unsupported type, circular reference)

The function uses json.Marshal internally, so the same rules apply:

  • Struct fields must be exported (capitalized) to be included
  • Use struct tags to control JSON field names: `json:"fieldName"`
  • Unexported fields are ignored
  • Nil pointers, empty slices, and zero values are included

Example:

type User struct {
    Name  string `json:"name"`
    Email string `json:"email"`
    Age   int    `json:"age"`
}

user := User{
    Name:  "Alice",
    Email: "alice@example.com",
    Age:   30,
}

jsonStr, err := ToString(user)
if err != nil {
    log.Fatal(err)
}

fmt.Println(jsonStr)
// Output: {"name":"Alice","email":"alice@example.com","age":30}

Example with slice:

users := []User{
    {Name: "Alice", Email: "alice@example.com", Age: 30},
    {Name: "Bob", Email: "bob@example.com", Age: 25},
}

jsonStr, err := ToString(users)
// Output: [{"name":"Alice",...},{"name":"Bob",...}]

func ToStringIndent

func ToStringIndent(input any, prefix string, indent string) (string, error)

ToStringIndent converts a Go struct to a formatted JSON string with indentation.

This function marshals the input struct into human-readable JSON format with custom prefix and indentation strings for each nesting level.

Parameters:

  • input: Any Go value to convert to JSON (typically a struct)
  • prefix: String to prepend to each line (typically empty "")
  • indent: String to use for each indentation level (e.g., "\t" or " ")

Returns:

  • string: Formatted JSON representation of the input
  • error: Error if marshaling fails

The prefix is added to the beginning of each line, and indent is added for each level of nesting in the JSON structure.

Example with tab indentation:

type Address struct {
    City    string `json:"city"`
    Country string `json:"country"`
}

type User struct {
    Name    string  `json:"name"`
    Age     int     `json:"age"`
    Address Address `json:"address"`
}

user := User{
    Name: "Alice",
    Age:  30,
    Address: Address{
        City:    "New York",
        Country: "USA",
    },
}

jsonStr, err := ToStringIndent(user, "", "\t")
if err != nil {
    log.Fatal(err)
}

fmt.Println(jsonStr)
// Output:
// {
//     "name": "Alice",
//     "age": 30,
//     "address": {
//         "city": "New York",
//         "country": "USA"
//     }
// }

Example with 2-space indentation:

jsonStr, err := ToStringIndent(user, "", "  ")
// Uses 2 spaces per indentation level

Example with prefix:

jsonStr, err := ToStringIndent(user, "> ", "  ")
// Each line starts with "> "

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL