json

package
v1.2.1 Latest Latest
Warning

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

Go to latest
Published: Jan 31, 2026 License: MIT Imports: 6 Imported by: 0

README

errx/json

JSON serialization for errx errors.

Overview

The errx/json package provides JSON serialization capabilities for errx errors while maintaining the zero-dependency principle of the core errx package. It serializes all errx error types including classification sentinels, displayable errors, attributed errors, and stack traces.

Installation

go get github.com/go-extras/errx/json

Usage

Basic Serialization
import (
    "github.com/go-extras/errx"
    errxjson "github.com/go-extras/errx/json"
)

err := errx.Wrap("failed to fetch user", cause, ErrNotFound)
jsonBytes, _ := errxjson.Marshal(err)
Pretty Printing
jsonBytes, _ := errxjson.MarshalIndent(err, "", "  ")
Convert to Struct
serialized := errxjson.ToSerializedError(err)
// Manipulate the struct before serializing
jsonBytes, _ := json.Marshal(serialized)

Configuration Options

WithMaxDepth

Limit the depth of error chain traversal to prevent issues with deeply nested or circular error chains.

jsonBytes, _ := errxjson.Marshal(err, errxjson.WithMaxDepth(16))
WithMaxStackFrames

Limit the number of stack frames included in the serialized output to reduce JSON size.

jsonBytes, _ := errxjson.Marshal(err, errxjson.WithMaxStackFrames(10))
WithIncludeStandardErrors

Control whether standard (non-errx) errors in the error chain are included.

// Only include errx errors
jsonBytes, _ := errxjson.Marshal(err, errxjson.WithIncludeStandardErrors(false))

JSON Structure

The serialized error has the following structure:

{
  "message": "error message from Error()",
  "display_text": "user-facing message (if displayable error present)",
  "sentinels": ["list", "of", "sentinel", "texts"],
  "attributes": [
    {"key": "user_id", "value": 123},
    {"key": "action", "value": "delete"}
  ],
  "stack_trace": [
    {
      "file": "/path/to/file.go",
      "line": 42,
      "function": "package.FunctionName"
    }
  ],
  "cause": {
    "message": "wrapped error"
  },
  "causes": [
    {"message": "error 1"},
    {"message": "error 2"}
  ]
}

Fields are omitted if empty (using omitempty tags).

Examples

Displayable Error
displayErr := errx.NewDisplayable("Invalid email address")
err := errx.Wrap("validation failed", displayErr)

jsonBytes, _ := errxjson.MarshalIndent(err, "", "  ")
// {
//   "message": "validation failed: Invalid email address",
//   "display_text": "Invalid email address",
//   "cause": {
//     "message": "Invalid email address",
//     "display_text": "Invalid email address"
//   }
// }
Error with Attributes
attrErr := errx.WithAttrs("user_id", 123, "action", "delete")
err := errx.Classify(baseErr, attrErr, ErrDatabase)

jsonBytes, _ := errxjson.Marshal(err)
Error with Stack Trace
import "github.com/go-extras/errx/stacktrace"

err := stacktrace.Wrap("operation failed", baseErr, ErrDatabase)
jsonBytes, _ := errxjson.Marshal(err)
// Stack trace will be included in the "stack_trace" field
Complex Error
baseErr := errors.New("connection timeout")
displayErr := errx.NewDisplayable("Service temporarily unavailable")
attrErr := errx.WithAttrs("retry_count", 3, "host", "localhost")

err := stacktrace.Wrap("database query failed",
    baseErr, displayErr, attrErr, ErrTimeout)

jsonBytes, _ := errxjson.MarshalIndent(err, "", "  ")

Use Cases

API Error Responses
func handleError(w http.ResponseWriter, err error) {
    jsonBytes, _ := errxjson.Marshal(err)
    w.Header().Set("Content-Type", "application/json")
    w.Write(jsonBytes)
}
Structured Logging
serialized := errxjson.ToSerializedError(err)
slog.Error("operation failed",
    "error_message", serialized.Message,
    "display_text", serialized.DisplayText,
    "attributes", serialized.Attributes)
Error Persistence
// Store error in database
jsonBytes, _ := errxjson.Marshal(err)
db.SaveError(jsonBytes)

// Later, read and inspect
var serialized errxjson.SerializedError
json.Unmarshal(jsonBytes, &serialized)

Design Principles

  1. Zero Dependencies: Uses only Go's standard library encoding/json
  2. No Deserialization: Only provides serialization (one-way), not deserialization
  3. Comprehensive Coverage: Handles all errx error types and standard errors
  4. Safety First: Includes circular reference detection and depth limits
  5. Flexible Configuration: Options for customizing serialization behavior

Limitations

  • No Deserialization: This package does not provide deserialization (JSON to error) functionality. Errors are runtime constructs and cannot be meaningfully reconstructed from JSON.
  • Sentinel Hierarchy: Parent sentinels in hierarchical relationships are not serialized - only direct sentinels are included.
  • Attribute Ordering: The order of attributes in the JSON output is stable for a given error but should not be relied upon for semantic meaning.

Documentation

Overview

Package json provides JSON serialization capabilities for errx errors.

This package extends errx with JSON serialization while keeping the core errx package minimal and zero-dependency. It serializes all errx error types including sentinels, displayable errors, attributed errors, and stack traces.

Basic Usage

err := errx.Wrap("failed to process", cause, ErrNotFound)
jsonBytes, err := json.Marshal(err)

Pretty Printing

jsonBytes, err := json.MarshalIndent(err, "", "  ")

Configuration

jsonBytes, err := json.Marshal(err,
    json.WithMaxDepth(16),
    json.WithMaxStackFrames(10))
Example (ApiResponse)

Example_apiResponse demonstrates using JSON serialization for API responses

package main

import (
	"fmt"

	"github.com/go-extras/errx"
	errxjson "github.com/go-extras/errx/json"
)

var ErrNotFound = errx.NewSentinel("not found")

func main() {
	// Simulate an error from the service layer
	displayErr := errx.NewDisplayable("User not found")
	attrErr := errx.Attrs("user_id", "12345")
	serviceErr := errx.Classify(displayErr, ErrNotFound, attrErr)

	// Serialize for API response
	jsonBytes, _ := errxjson.MarshalIndent(serviceErr, "", "  ")
	fmt.Println(string(jsonBytes))

}
Output:
{
  "message": "User not found",
  "display_text": "User not found",
  "sentinels": [
    "not found"
  ],
  "attributes": [
    {
      "key": "user_id",
      "value": "12345"
    }
  ],
  "cause": {
    "message": "User not found",
    "display_text": "User not found"
  }
}
Example (AttributedError)

Example_attributedError demonstrates serializing errors with attributes

package main

import (
	"errors"
	"fmt"

	"github.com/go-extras/errx"
	errxjson "github.com/go-extras/errx/json"
)

var ErrDatabase = errx.NewSentinel("database")

func main() {
	attrErr := errx.Attrs(
		"user_id", 12345,
		"action", "delete",
		"resource", "account",
	)
	err := errx.Classify(errors.New("operation failed"), attrErr, ErrDatabase)

	jsonBytes, _ := errxjson.MarshalIndent(err, "", "  ")
	fmt.Println(string(jsonBytes))

}
Output:
{
  "message": "operation failed",
  "sentinels": [
    "database"
  ],
  "attributes": [
    {
      "key": "user_id",
      "value": 12345
    },
    {
      "key": "action",
      "value": "delete"
    },
    {
      "key": "resource",
      "value": "account"
    }
  ],
  "cause": {
    "message": "operation failed"
  }
}
Example (ComplexError)

Example_complexError demonstrates serializing errors with all features

package main

import (
	"errors"
	"fmt"

	"github.com/go-extras/errx"
	errxjson "github.com/go-extras/errx/json"
	"github.com/go-extras/errx/stacktrace"
)

var ErrDatabase = errx.NewSentinel("database")

func main() {
	// Build a rich error with all features
	baseErr := errors.New("connection timeout")
	displayErr := errx.NewDisplayable("Service temporarily unavailable")
	attrErr := errx.Attrs("retry_count", 3, "host", "localhost")

	err := stacktrace.Wrap("database query failed",
		baseErr, displayErr, attrErr, ErrDatabase)

	serialized := errxjson.ToSerializedError(err)

	fmt.Printf("Has display text: %v\n", serialized.DisplayText != "")
	fmt.Printf("Has attributes: %v\n", len(serialized.Attributes) > 0)
	fmt.Printf("Has stack trace: %v\n", len(serialized.StackTrace) > 0)
	fmt.Printf("Has sentinels: %v\n", len(serialized.Sentinels) > 0)

}
Output:
Has display text: true
Has attributes: true
Has stack trace: true
Has sentinels: true
Example (DisplayableError)

Example_displayableError demonstrates serializing displayable errors

package main

import (
	"fmt"

	"github.com/go-extras/errx"
	errxjson "github.com/go-extras/errx/json"
)

func main() {
	displayErr := errx.NewDisplayable("Invalid email address")
	err := errx.Wrap("validation failed", displayErr)

	jsonBytes, _ := errxjson.MarshalIndent(err, "", "  ")
	fmt.Println(string(jsonBytes))

}
Output:
{
  "message": "validation failed: Invalid email address",
  "display_text": "Invalid email address",
  "cause": {
    "message": "Invalid email address",
    "display_text": "Invalid email address"
  }
}
Example (ErrorChain)

Example_errorChain demonstrates serializing error chains

package main

import (
	"errors"
	"fmt"

	"github.com/go-extras/errx"
	errxjson "github.com/go-extras/errx/json"
)

var (
	ErrNotFound = errx.NewSentinel("not found")
	ErrDatabase = errx.NewSentinel("database")
)

func main() {
	err1 := errors.New("root cause")
	err2 := errx.Wrap("middle layer", err1, ErrDatabase)
	err3 := errx.Wrap("top layer", err2, ErrNotFound)

	serialized := errxjson.ToSerializedError(err3)

	// Walk the chain
	fmt.Println("Top:", serialized.Message)
	if serialized.Cause != nil {
		fmt.Println("Middle:", serialized.Cause.Message)
		if serialized.Cause.Cause != nil {
			fmt.Println("Root:", serialized.Cause.Cause.Message)
		}
	}

}
Output:
Top: top layer: middle layer: root cause
Middle: middle layer: root cause
Root: root cause

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Marshal

func Marshal(err error, opts ...Option) ([]byte, error)

Marshal serializes an error to JSON bytes. It returns nil, nil for nil errors.

Example:

err := errx.Wrap("failed", cause, ErrNotFound)
jsonBytes, err := json.Marshal(err)
Example

ExampleMarshal demonstrates basic JSON serialization of errx errors

package main

import (
	"errors"
	"fmt"

	"github.com/go-extras/errx"
	errxjson "github.com/go-extras/errx/json"
)

var ErrNotFound = errx.NewSentinel("not found")

func main() {
	err := errx.Wrap("failed to fetch user", errors.New("connection timeout"), ErrNotFound)

	jsonBytes, _ := errxjson.Marshal(err)
	fmt.Println(string(jsonBytes))

}
Output:
{"message":"failed to fetch user: connection timeout","sentinels":["not found"],"cause":{"message":"connection timeout"}}

func MarshalIndent

func MarshalIndent(err error, prefix, indent string, opts ...Option) ([]byte, error)

MarshalIndent serializes an error to pretty-printed JSON bytes. It returns nil, nil for nil errors.

Example:

jsonBytes, err := json.MarshalIndent(err, "", "  ")
Example

ExampleMarshalIndent demonstrates pretty-printed JSON serialization

package main

import (
	"fmt"

	"github.com/go-extras/errx"
	errxjson "github.com/go-extras/errx/json"
)

var ErrNotFound = errx.NewSentinel("not found")

func main() {
	displayErr := errx.NewDisplayable("User not found")
	err := errx.Wrap("lookup failed", displayErr, ErrNotFound)

	jsonBytes, _ := errxjson.MarshalIndent(err, "", "  ")
	fmt.Println(string(jsonBytes))

}
Output:
{
  "message": "lookup failed: User not found",
  "display_text": "User not found",
  "sentinels": [
    "not found"
  ],
  "cause": {
    "message": "User not found",
    "display_text": "User not found"
  }
}

Types

type Option

type Option func(*config)

Option is a function that configures the JSON serialization behavior.

func WithIncludeStandardErrors

func WithIncludeStandardErrors(include bool) Option

WithIncludeStandardErrors controls whether standard (non-errx) errors in the error chain are included in the serialized output. The default is true.

When set to false, only errx errors (those implementing errx.Classified) will be serialized in the cause chain. Standard errors will be skipped.

Example:

// Only include errx errors, skip standard errors
jsonBytes, err := json.Marshal(err, json.WithIncludeStandardErrors(false))
Example

ExampleWithIncludeStandardErrors demonstrates filtering error types

package main

import (
	"errors"
	"fmt"

	"github.com/go-extras/errx"
	errxjson "github.com/go-extras/errx/json"
)

var ErrDatabase = errx.NewSentinel("database")

func main() {
	// Mix of errx and standard errors
	stdErr := errors.New("standard error")
	err := errx.Wrap("wrapper", stdErr, ErrDatabase)

	// Exclude standard errors
	serialized := errxjson.ToSerializedError(err, errxjson.WithIncludeStandardErrors(false))

	// The standard error is skipped
	fmt.Printf("Has cause: %v\n", serialized.Cause != nil)
	fmt.Printf("Sentinels: %v\n", serialized.Sentinels)

}
Output:
Has cause: false
Sentinels: [database]

func WithMaxDepth

func WithMaxDepth(depth int) Option

WithMaxDepth sets the maximum depth for traversing error chains. This prevents issues with deeply nested or potentially circular error chains. The default is 32.

When the depth limit is reached, the serialized error will have a message of "(max depth reached)" and no further unwrapping will occur.

Example:

jsonBytes, err := json.Marshal(err, json.WithMaxDepth(10))
Example

ExampleWithMaxDepth demonstrates limiting error chain depth

package main

import (
	"errors"
	"fmt"

	"github.com/go-extras/errx"
	errxjson "github.com/go-extras/errx/json"
)

func main() {
	// Create a deep error chain
	err := errors.New("level 3")
	err = errx.Wrap("level 2", err)
	err = errx.Wrap("level 1", err)

	// Limit serialization to 2 levels
	jsonBytes, _ := errxjson.Marshal(err, errxjson.WithMaxDepth(2))
	fmt.Println(string(jsonBytes))

}
Output:
{"message":"level 1: level 2: level 3","cause":{"message":"level 2: level 3","cause":{"message":"(max depth reached)"}}}

func WithMaxStackFrames

func WithMaxStackFrames(frames int) Option

WithMaxStackFrames sets the maximum number of stack frames to include in the serialized output. This prevents excessive JSON size when errors have deep stack traces. The default is 32.

If the stack trace has more frames than the limit, only the first N frames will be included in the serialized output.

Example:

jsonBytes, err := json.Marshal(err, json.WithMaxStackFrames(10))
Example

ExampleWithMaxStackFrames demonstrates limiting stack trace frames

package main

import (
	"errors"
	"fmt"

	errxjson "github.com/go-extras/errx/json"
	"github.com/go-extras/errx/stacktrace"
)

func main() {
	err := stacktrace.Wrap("operation failed", errors.New("base error"))

	// Limit to 3 stack frames
	serialized := errxjson.ToSerializedError(err, errxjson.WithMaxStackFrames(3))
	fmt.Printf("Stack frames: %d\n", len(serialized.StackTrace))
	fmt.Println("Has stack trace: true")

}
Output:
Stack frames: 3
Has stack trace: true

type SerializedAttr

type SerializedAttr struct {
	Key   string `json:"key"`
	Value any    `json:"value"`
}

SerializedAttr represents a single attribute key-value pair.

type SerializedError

type SerializedError struct {
	// Message is the error message from Error()
	Message string `json:"message"`

	// DisplayText contains the displayable error message if one exists
	DisplayText string `json:"display_text,omitempty"`

	// Sentinels lists all classification sentinel texts found in this error
	Sentinels []string `json:"sentinels,omitempty"`

	// Attributes contains structured key-value pairs attached to this error
	Attributes []SerializedAttr `json:"attributes,omitempty"`

	// StackTrace contains stack frames if a stack trace was captured
	StackTrace []SerializedFrame `json:"stack_trace,omitempty"`

	// Cause is the wrapped error (single unwrap)
	Cause *SerializedError `json:"cause,omitempty"`

	// Causes contains multiple wrapped errors (multi-error unwrap)
	Causes []*SerializedError `json:"causes,omitempty"`
}

SerializedError represents the JSON structure of an errx error. It captures all aspects of an errx error including classifications, attributes, stack traces, and the error chain.

func ToSerializedError

func ToSerializedError(err error, opts ...Option) *SerializedError

ToSerializedError converts an error to a SerializedError struct. It returns nil for nil errors. This is useful when you want to manipulate the structure before serializing.

Example:

serialized := json.ToSerializedError(err)
// Manipulate serialized...
jsonBytes, _ := json.Marshal(serialized)
Example

ExampleToSerializedError demonstrates converting an error to a struct

package main

import (
	"errors"
	"fmt"

	"github.com/go-extras/errx"
	errxjson "github.com/go-extras/errx/json"
)

var ErrDatabase = errx.NewSentinel("database")

func main() {
	attrErr := errx.Attrs("user_id", 42)
	err := errx.Classify(errors.New("operation failed"), attrErr, ErrDatabase)

	serialized := errxjson.ToSerializedError(err)
	fmt.Printf("Message: %s\n", serialized.Message)
	fmt.Printf("Attributes: %d\n", len(serialized.Attributes))
	fmt.Printf("Sentinels: %v\n", serialized.Sentinels)

}
Output:
Message: operation failed
Attributes: 1
Sentinels: [database]

type SerializedFrame

type SerializedFrame struct {
	File     string `json:"file"`
	Line     int    `json:"line"`
	Function string `json:"function"`
}

SerializedFrame represents a single stack frame.

Jump to

Keyboard shortcuts

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