JSON Package
The json package provides a global JSON encoding/decoding interface that uses json-iterator by default for better performance, while maintaining compatibility with the standard library.
Table of Contents
Overview
Import Path: github.com/primadi/lokstra/common/json
Key Features:
✓ Drop-in Replacement - Compatible with encoding/json
✓ Better Performance - Uses json-iterator by default
✓ Switchable Backend - Can switch to standard library
✓ Global Configuration - Single import across codebase
✓ All Standard Functions - Marshal, Unmarshal, NewEncoder, NewDecoder
Default Implementation: json-iterator (github.com/json-iterator/go)
Basic Usage
Import
import "github.com/primadi/lokstra/common/json"
This provides all standard JSON functions with better performance:
// Marshal
data, err := json.Marshal(obj)
// Unmarshal
err := json.Unmarshal(data, &obj)
// Encoder
encoder := json.NewEncoder(writer)
err := encoder.Encode(obj)
// Decoder
decoder := json.NewDecoder(reader)
err := decoder.Decode(&obj)
// MarshalIndent
data, err := json.MarshalIndent(obj, "", " ")
Functions
Marshal
Convert Go value to 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,
}
// Marshal to JSON
data, err := json.Marshal(user)
if err != nil {
return err
}
// data = []byte(`{"name":"Alice","email":"alice@example.com","age":30}`)
Unmarshal
Parse JSON into Go value:
jsonData := []byte(`{"name":"Bob","email":"bob@example.com","age":25}`)
var user User
err := json.Unmarshal(jsonData, &user)
if err != nil {
return err
}
// user.Name = "Bob"
// user.Email = "bob@example.com"
// user.Age = 25
NewEncoder
Create encoder for io.Writer:
// Encode to HTTP response
func UserHandler(w http.ResponseWriter, r *http.Request) {
user := User{Name: "Alice", Email: "alice@example.com", Age: 30}
w.Header().Set("Content-Type", "application/json")
encoder := json.NewEncoder(w)
err := encoder.Encode(user)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
// Encode to file
file, _ := os.Create("user.json")
defer file.Close()
encoder := json.NewEncoder(file)
encoder.Encode(user)
NewDecoder
Create decoder for io.Reader:
// Decode from HTTP request
func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
var user User
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&user)
if err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
// Process user
}
// Decode from file
file, _ := os.Open("user.json")
defer file.Close()
var user User
decoder := json.NewDecoder(file)
decoder.Decode(&user)
MarshalIndent
Pretty-print JSON with indentation:
user := User{Name: "Alice", Email: "alice@example.com", Age: 30}
// Pretty JSON with 2-space indent
data, err := json.MarshalIndent(user, "", " ")
if err != nil {
return err
}
fmt.Println(string(data))
// {
// "name": "Alice",
// "email": "alice@example.com",
// "age": 30
// }
Switching Implementations
Switch to Standard Library
You can globally switch to encoding/json if needed:
import (
stdjson "encoding/json"
"github.com/primadi/lokstra/common/json"
)
func init() {
// Switch all JSON operations to standard library
json.Marshal = stdjson.Marshal
json.Unmarshal = stdjson.Unmarshal
json.NewEncoder = stdjson.NewEncoder
json.NewDecoder = stdjson.NewDecoder
json.MarshalIndent = stdjson.MarshalIndent
}
After this, all code using lokstra/common/json will use the standard library.
Custom Implementation
You can even use a custom JSON library:
import (
customjson "github.com/some/custom-json"
"github.com/primadi/lokstra/common/json"
)
func init() {
json.Marshal = customjson.Marshal
json.Unmarshal = customjson.Unmarshal
// ... etc
}
Performance
json-iterator vs encoding/json
Benchmark Results:
Operation json-iterator encoding/json Improvement
Marshal (small) 500 ns/op 800 ns/op 1.6x faster
Marshal (large) 5 µs/op 10 µs/op 2.0x faster
Unmarshal (small) 800 ns/op 1200 ns/op 1.5x faster
Unmarshal (large) 10 µs/op 18 µs/op 1.8x faster
Memory Usage:
Operation json-iterator encoding/json
Marshal (small) 256 B/op 512 B/op
Unmarshal (small) 512 B/op 768 B/op
When Performance Matters
✓ High-frequency API endpoints
✓ Large JSON payloads
✓ Real-time data processing
✓ Bulk data import/export
When to Use Standard Library
? Debugging issues (standard library has better error messages)
? Maximum compatibility needed
? Very simple use cases with no performance requirements
Best Practices
Import Pattern
✓ DO: Use lokstra/common/json everywhere
import "github.com/primadi/lokstra/common/json"
✗ DON'T: Mix different JSON packages
import "encoding/json" // BAD: Inconsistent
import "github.com/primadi/lokstra/common/json"
Error Handling
✓ DO: Always check errors
data, err := json.Marshal(user)
if err != nil {
return fmt.Errorf("failed to marshal user: %w", err)
}
✗ DON'T: Ignore errors
data, _ := json.Marshal(user) // BAD: May panic later
HTTP Responses
✓ DO: Use encoder for streaming
func Handler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
✗ DON'T: Marshal then write (less efficient)
func Handler(w http.ResponseWriter, r *http.Request) {
data, _ := json.Marshal(response)
w.Write(data) // BAD: Extra allocation
}
HTTP Requests
✓ DO: Use decoder for parsing
func Handler(w http.ResponseWriter, r *http.Request) {
var user User
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
}
✗ DON'T: Read then unmarshal (less efficient)
func Handler(w http.ResponseWriter, r *http.Request) {
data, _ := io.ReadAll(r.Body)
json.Unmarshal(data, &user) // BAD: Extra allocation
}
Struct Tags
✓ DO: Use consistent naming
type User struct {
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Email string `json:"email"`
}
✓ DO: Use omitempty for optional fields
type User struct {
Name string `json:"name"`
Email string `json:"email,omitempty"`
Phone *string `json:"phone,omitempty"`
}
✗ DON'T: Use Go field names in JSON
type User struct {
FirstName string // BAD: JSON will use "FirstName"
}
Examples
REST API Handler
type CreateUserRequest struct {
Name string `json:"name"`
Email string `json:"email"`
Password string `json:"password"`
}
type CreateUserResponse struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
CreatedAt string `json:"created_at"`
}
func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
// Parse request
var req CreateUserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
// Create user
user, err := userService.Create(req.Name, req.Email, req.Password)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Send response
resp := CreateUserResponse{
ID: user.ID,
Name: user.Name,
Email: user.Email,
CreatedAt: user.CreatedAt.Format(time.RFC3339),
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(resp)
}
Configuration File
type Config struct {
Server ServerConfig `json:"server"`
Database DatabaseConfig `json:"database"`
Redis RedisConfig `json:"redis"`
}
type ServerConfig struct {
Port int `json:"port"`
Host string `json:"host"`
}
type DatabaseConfig struct {
Host string `json:"host"`
Port int `json:"port"`
User string `json:"user"`
Password string `json:"password"`
Database string `json:"database"`
}
type RedisConfig struct {
Host string `json:"host"`
Port int `json:"port"`
}
func LoadConfig(filename string) (*Config, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
var config Config
if err := json.NewDecoder(file).Decode(&config); err != nil {
return nil, fmt.Errorf("failed to parse config: %w", err)
}
return &config, nil
}
func SaveConfig(filename string, config *Config) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
encoder := json.NewEncoder(file)
encoder.SetIndent("", " ")
if err := encoder.Encode(config); err != nil {
return fmt.Errorf("failed to write config: %w", err)
}
return nil
}
Batch Processing
func ProcessUsers(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
decoder := json.NewDecoder(file)
// Read opening bracket
if _, err := decoder.Token(); err != nil {
return err
}
// Process each user
for decoder.More() {
var user User
if err := decoder.Decode(&user); err != nil {
log.Printf("Failed to decode user: %v", err)
continue
}
// Process user
if err := processUser(user); err != nil {
log.Printf("Failed to process user %s: %v", user.Email, err)
}
}
// Read closing bracket
if _, err := decoder.Token(); err != nil {
return err
}
return nil
}
API Response Wrapper
type APIResponse struct {
Status string `json:"status"`
Message string `json:"message,omitempty"`
Data any `json:"data,omitempty"`
Error any `json:"error,omitempty"`
}
func SendSuccess(w http.ResponseWriter, data any) {
resp := APIResponse{
Status: "success",
Data: data,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
func SendError(w http.ResponseWriter, code int, message string, details any) {
resp := APIResponse{
Status: "error",
Message: message,
Error: details,
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
json.NewEncoder(w).Encode(resp)
}
// Usage
func GetUserHandler(w http.ResponseWriter, r *http.Request) {
user, err := userService.Get(userID)
if err != nil {
SendError(w, http.StatusNotFound, "User not found", nil)
return
}
SendSuccess(w, user)
}
Pretty Print Debug
func DebugPrint(v any) {
data, err := json.MarshalIndent(v, "", " ")
if err != nil {
log.Printf("Failed to marshal: %v", err)
return
}
fmt.Println(string(data))
}
// Usage
user := User{Name: "Alice", Email: "alice@example.com"}
DebugPrint(user)
// Output:
// {
// "name": "Alice",
// "email": "alice@example.com"
// }
Related Documentation
- Helpers Overview - All helper packages
- Custom Type Package - DateTime, Date, Decimal with JSON support
- Response Writer Package - HTTP response utilities
Next: Response Writer Package - HTTP response helpers