jsonrpc

package module
v0.9.0 Latest Latest
Warning

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

Go to latest
Published: Jun 16, 2026 License: MIT Imports: 13 Imported by: 1

README

jsonrpc

Go Documentation Go Report Card MIT License

A JSON-RPC 2.0 implementation in Go.

Utilizes the bytedance/sonic library for JSON serialization.

Attempts to conform fully to the JSON-RPC 2.0 Specification, with a few minor exceptions:

  • The id field is allowed to be fractional numbers, in addition to integers and strings. The specification notes that "numbers should not contain fractional parts", but this library allows them for convenience.
  • Error handling
    • Error codes can be zero if a message is provided (the specification allows any integer).
    • The error field is flexible in unmarshaling, with fallback logic to handle various error formats for custom error handling downstream.

Install

go get github.com/jkbrsn/jsonrpc

Usage

Single Requests and Responses
Creating and Encoding a Request
// Create a request with auto-generated ID
req := jsonrpc.NewRequest("sum", []any{1, 2})

// Or create with a specific ID
req := jsonrpc.NewRequestWithID("sum", []any{1, 2}, "my-id")

// Encode to JSON
data, err := req.MarshalJSON()
Decoding and Handling a Response
// Decode a response from JSON
resp, err := jsonrpc.DecodeResponse(data)
if err != nil {
    // Handle decode error
}

// Check for JSON-RPC error
if resp.Err() != nil {
    fmt.Printf("RPC Error: %s\n", resp.Err().Message)
    return
}

// Unmarshal the result into your type
var result int
if err := resp.UnmarshalResult(&result); err != nil {
    // Handle unmarshal error
}
fmt.Printf("Result: %d\n", result)
Creating a Notification
// Notifications are requests without IDs (no response expected)
notification := jsonrpc.NewNotification("log", map[string]any{
    "level": "info",
    "message": "Operation completed",
})
Working with Params

The library supports both positional (array) and named (object) parameters, as well as structured parameter unmarshaling.

Positional Parameters
// Create a request with array parameters
req := jsonrpc.NewRequest("subtract", []any{42, 23})
// Params: [42, 23]
Named Parameters
// Create a request with object parameters
req := jsonrpc.NewRequest("updateUser", map[string]any{
    "userId": 123,
    "name":   "Alice",
    "active": true,
})
// Params: {"userId": 123, "name": "Alice", "active": true}
Unmarshaling Params into Structs
// Define your parameter structure
type UserParams struct {
    Name  string `json:"name"`
    Email string `json:"email"`
    Age   int    `json:"age"`
}

// Unmarshal params into the struct
var params UserParams
if err := req.UnmarshalParams(&params); err != nil {
    // Handle error
}
// Use params.Name, params.Email, params.Age
Batch Requests and Responses

The library supports JSON-RPC 2.0 batch operations for sending multiple requests or responses in a single call.

Encoding a Batch Request
reqs := []*jsonrpc.Request{
    jsonrpc.NewRequest("sum", []any{1, 2}),
    jsonrpc.NewRequest("subtract", []any{5, 3}),
}
data, err := jsonrpc.EncodeBatchRequest(reqs)

// Or use the helper:
reqs, err := jsonrpc.NewBatchRequest(
    []string{"sum", "subtract"},
    []any{[]any{1, 2}, []any{5, 3}},
)
Decoding a Batch Response
resps, err := jsonrpc.DecodeBatchResponse(data)
for _, resp := range resps {
    if resp.Err() != nil {
        // Handle error
    } else {
        var result int
        resp.UnmarshalResult(&result)
    }
}
Auto-detecting Single vs Batch
resps, isBatch, err := jsonrpc.DecodeResponseOrBatch(data)
if isBatch {
    fmt.Printf("Received batch with %d responses\n", len(resps))
} else {
    fmt.Println("Received single response")
}
Notifications in Batches

Batches can contain notifications (requests without IDs). The server should not send responses for notifications:

reqs, err := jsonrpc.NewBatchNotification(
    []string{"log", "notify"},
    []any{map[string]any{"level": "info"}, map[string]any{"message": "test"}},
)
JSON-RPC 1.0 Compatibility

By default the decoder is strict JSON-RPC 2.0. Pass WithAllowJSONRPC1() to any decode entry point to also accept 1.0 payloads, such as those spoken by Bitcoin Core (no jsonrpc field, and result and error both present with error: null). The encode path is unaffected and always emits strict 2.0.

// Bitcoin Core-style 1.0 response: {"result":953931,"error":null,"id":1}
resp, err := jsonrpc.DecodeResponse(data, jsonrpc.WithAllowJSONRPC1())
if err != nil {
    // Without the option, this input is rejected as an invalid version.
}

var blockCount int
resp.UnmarshalResult(&blockCount)

The option works the same way for requests and batches:

req, err := jsonrpc.DecodeRequest(data, jsonrpc.WithAllowJSONRPC1())
resps, err := jsonrpc.DecodeBatchResponse(data, jsonrpc.WithAllowJSONRPC1())
reqs, isBatch, err := jsonrpc.DecodeRequestOrBatch(data, jsonrpc.WithAllowJSONRPC1())

Note that an explicit "error": null alongside a result is accepted even for 2.0 (without the option), since a null error is treated as no error.

Performance

This library is optimized for high-throughput server applications using several techniques:

Performance Profiles

The library provides five performance profiles that allow you to choose the right trade-off between speed, safety, and compatibility:

// Use balanced profile for production (recommended)
jsonrpc.SetPerformanceProfile(jsonrpc.ProfileBalanced)

// Or choose another profile based on your needs
jsonrpc.SetPerformanceProfile(jsonrpc.ProfileFast)

Available Profiles:

  • ProfileDefault (default): Sonic's efficient defaults - recommended for most usess
  • ProfileCompatible: Mimics encoding/json behavior for a slower, but deterministic, output with sorted keys
  • ProfileBalanced: Safe performance optimizations, ~5-10% faster than default with effectively zero risk
  • ProfileFast: Sonic's ConfigFastest - skips some validation for internal services
  • ProfileAggressive: Maximum speed, minimum validation - only for experts with controlled input

Choosing a Profile:

Profile Best For Performance Safety
ProfileDefault General use, untrusted input Baseline High
ProfileCompatible Migration from encoding/json Slower High
ProfileBalanced Production apps +5-10% High
ProfileFast Internal services +10-15% Medium
ProfileAggressive HFT, real-time systems +15-20% Low

See performance.go for detailed documentation on each profile.

Codec Pre-compilation (Enabled by Default)

The library pre-compiles JSON codecs at startup using sonic.Pretouch, which eliminates JIT compilation overhead on the first marshal/unmarshal operation. This provides:

  • 80-99% improvement in first-call latency: From ~1-5ms down to ~10-50μs
  • Better P99 latency: No unpredictable "warm-up" spike
  • Small startup cost: ~10-50ms additional initialization time

For CLI tools or single-shot programs, this optimization can be disabled to avoid the startup cost:

go build -tags nopretouch
Buffer Pooling

Stream reading operations (DecodeResponseFromReader, DecodeBatchRequestFromReader, etc.) use sync.Pool for buffer reuse, reducing GC pressure in high-throughput scenarios.

Lazy Unmarshaling

Response objects use lazy unmarshaling for ID and Error fields, deferring parsing until accessed. This is beneficial when handling large batches where you may not need to inspect every field.

ID Byte Caching

Response IDs are marshaled once and cached, avoiding re-marshaling on every MarshalJSON or WriteTo call. This is most valuable when responses are marshaled multiple times (e.g., for caching or retries).

Release Process

See release.yml for the release process specifics.

Release Workflow Behavior

The Manual Release workflow enforces consistent versioning rules:

  • version = 1.5.0, prerelease = true → creates v1.5.0-rc.1 (or the next -rc.N if others exist).
  • version = 1.5.0-rc.7, prerelease = true → creates exactly v1.5.0-rc.7 (if not already tagged).
  • version = v1.5.0, prerelease = false → creates final v1.5.0 (allowed even if prereleases exist).
  • If final v1.5.0 already exists → both prerelease = true and final runs for 1.5.0 are blocked.
  • Base version bumping → the base X.Y.Z must always be strictly greater than the latest existing final tag in the repo.

This means you can iterate with prerelease = true and later “promote” the same base to a final, but you cannot reuse or downgrade existing finals.

Contributing

For contributions, please open a GitHub issue with your questions and suggestions. Before submitting an issue, have a look at the existing TODO list to see if your idea is already in the works.

Documentation

Overview

Package jsonrpc provides a Go implementation of the JSON-RPC 2.0 specification, as well as tools to parse and work with JSON-RPC requests and responses.

Index

Examples

Constants

View Source
const (
	InvalidRequest      = -32600
	MethodNotFound      = -32601
	InvalidParams       = -32602
	ServerSideException = -32603
	ParseError          = -32700
)

JSON-RPC error codes

Variables

This section is empty.

Functions

func EncodeBatchRequest

func EncodeBatchRequest(reqs []*Request) ([]byte, error)

EncodeBatchRequest marshals a slice of JSON-RPC requests into a batch. Returns an error if: - Input slice is empty - Any request fails validation

func EncodeBatchResponse

func EncodeBatchResponse(resps []*Response) ([]byte, error)

EncodeBatchResponse marshals a slice of JSON-RPC responses into a batch. Returns an error if: - Input slice is empty - Any response fails validation

func RandomJSONRPCID

func RandomJSONRPCID() int64

RandomJSONRPCID returns a randomly generated value appropriate for a JSON-RPC ID field. Returns an int64 in the range [0, 2147483647] (int32 range) for compatibility.

func SetPerformanceProfile added in v0.8.0

func SetPerformanceProfile(profile PerformanceProfile)

SetPerformanceProfile configures the JSON encoding/decoding behavior for all operations. This function is thread-safe and affects all subsequent JSON operations in the package.

The available profiles are:

  • ProfileDefault: Recommended for most users (efficient + safe)
  • ProfileCompatible: Use when migrating from encoding/json
  • ProfileBalanced: Production apps wanting safe optimizations
  • ProfileFast: Internal services with controlled data
  • ProfileAggressive: Maximum speed, use with caution

Example usage:

// Use the balanced profile
jsonrpc.SetPerformanceProfile(jsonrpc.ProfileBalanced)

Types

type DecodeOption added in v0.9.0

type DecodeOption func(*decodeOptions)

DecodeOption configures decoding behavior. Pass options to decode entry points such as DecodeRequest, DecodeResponse, and their batch variants. Options only affect decoding; the encode path always emits strict JSON-RPC 2.0.

func WithAllowJSONRPC1 added in v0.9.0

func WithAllowJSONRPC1() DecodeOption

WithAllowJSONRPC1 relaxes version validation on decode to accept JSON-RPC 1.0 payloads, which carry no "jsonrpc" field (or an explicit "jsonrpc":"1.0") and may include both result and a null error. The decoded version string is preserved as-is. Without this option, decoding stays strict JSON-RPC 2.0.

type Error

type Error struct {
	Code    int    `json:"code,omitempty"`
	Message string `json:"message,omitempty"`
	Data    any    `json:"data,omitempty"` // Optional data field
}

Error represents a JSON-RPC error.

Example (Data)

ExampleError_data demonstrates using the Data field in errors.

// Create an error with additional data
err := &Error{
	Code:    -32602,
	Message: "Invalid params",
	Data: map[string]any{
		"field":  "email",
		"reason": "invalid format",
	},
}

fmt.Printf("Code: %d\n", err.Code)
fmt.Printf("Message: %s\n", err.Message)
fmt.Printf("Data type: %T\n", err.Data)
Output:
Code: -32602
Message: Invalid params
Data type: map[string]interface {}

func (*Error) Equals

func (e *Error) Equals(other *Error) bool

Equals compares the contents of two JSON-RPC errors for equality. Returns true if both errors have the same Code and Message.

Note: The Data field is intentionally excluded from comparison because:

  1. Data has type `any`, making deep comparison complex and expensive
  2. Error equality is typically determined by code + message alone
  3. Data is optional and used for supplementary information

If you need to compare Data fields, do so separately after calling Equals().

func (*Error) IsEmpty

func (e *Error) IsEmpty() bool

IsEmpty returns true if the error is empty, which is if the error is nil or both code and message are empty.

Note: Zero error codes are valid per JSON-RPC 2.0 spec, but are treated as "empty" when both code=0 and message="". This helps identify placeholder or uninitialized errors.

func (*Error) String

func (e *Error) String() string

String returns a string representation of the error.

func (*Error) UnmarshalJSON

func (e *Error) UnmarshalJSON(data []byte) error

UnmarshalJSON unmarshals an error from a raw JSON-RPC response. The unmarshal logic uses several fallbacks to ensure an error is produced.

func (*Error) Validate

func (e *Error) Validate() error

Validate checks if the error is valid according to the JSON-RPC specification. An error is valid if it has at least one of: a non-zero code or a non-empty message. Zero error codes are allowed per JSON-RPC 2.0 spec, though they're treated as "empty" by IsEmpty() when combined with an empty message.

type PerformanceProfile added in v0.8.0

type PerformanceProfile int

PerformanceProfile defines a set of options for the sonic JSON parser, optimized for different usecases. Each profile represents a trade-off between performance, safety, and compatibility.

const (
	// ProfileDefault uses sonic's default configuration, providing a balance of efficiency
	// and safety. Provides excellent performance without sacrificing robustness.
	ProfileDefault PerformanceProfile = iota

	// ProfileCompatible mimics encoding/json behavior for maximum compatibility.
	// Use this when migrating from encoding/json and you need identical output formatting.
	//
	// Note: This is slower than ProfileDefault due to sorting and escaping.
	ProfileCompatible

	// ProfileBalanced applies safe performance optimizations without compromising robustness.
	// This profile disables unnecessary features (HTML escaping, key sorting) while keeping
	// all safety validations enabled.
	ProfileBalanced

	// ProfileFast uses sonic's official ConfigFastest settings, prioritizing speed while
	// maintaining reasonable safety. Skips validation of already-validated JSON.
	//
	// Based on the sonic.ConfigFastest configuration.
	ProfileFast

	// ProfileAggressive maximizes performance by disabling most validation and safety features.
	// Only use this when you have complete control over input sources and have profiled to
	// confirm JSON encoding is a bottleneck.
	//
	// WARNING: Can panic or produce invalid JSON with malformed input.
	ProfileAggressive
)

func GetPerformanceProfile added in v0.8.0

func GetPerformanceProfile() PerformanceProfile

GetPerformanceProfile returns the currently active performance profile.

type Request

type Request struct {
	JSONRPC string `json:"jsonrpc"`
	ID      any    `json:"id,omitempty"`
	Method  string `json:"method"`
	Params  any    `json:"params,omitempty"`
}

Request is a struct for a JSON-RPC request. It conforms to the JSON-RPC 2.0 specification except that the ID field is allowed to be fractional.

Example (Params_named)

ExampleRequest_params_named demonstrates using named parameters (object).

// Create a request with named parameters
req := NewRequest("updateUser", map[string]any{
	"userId": 123,
	"name":   "Alice",
	"active": true,
})

fmt.Printf("Method: %s\n", req.Method)
fmt.Printf("Params type: %T\n", req.Params)
Output:
Method: updateUser
Params type: map[string]interface {}
Example (Params_nil)

ExampleRequest_params_nil demonstrates a request without parameters.

// Create a request with no parameters
req := NewRequest("getServerTime", nil)

fmt.Printf("Method: %s\n", req.Method)
fmt.Printf("Params: %v\n", req.Params)
Output:
Method: getServerTime
Params: <nil>
Example (Params_positional)

ExampleRequest_params_positional demonstrates using positional parameters (array).

// Create a request with positional parameters
req := NewRequest("subtract", []any{42, 23})

fmt.Printf("Method: %s\n", req.Method)
fmt.Printf("Params type: %T\n", req.Params)
fmt.Printf("Params: %v\n", req.Params)
Output:
Method: subtract
Params type: []interface {}
Params: [42 23]

func DecodeBatchRequest

func DecodeBatchRequest(data []byte, opts ...DecodeOption) ([]*Request, error)

DecodeBatchRequest parses a JSON-RPC batch request from a byte slice. Returns an error if: - Input is not a JSON array - Array is empty - Any element fails to parse as a valid Request

func DecodeBatchRequestFromReader

func DecodeBatchRequestFromReader(
	r io.Reader, expectedSize int, opts ...DecodeOption,
) ([]*Request, error)

DecodeBatchRequestFromReader parses a JSON-RPC batch request from an io.Reader.

func DecodeRequest

func DecodeRequest(data []byte, opts ...DecodeOption) (*Request, error)

DecodeRequest parses a JSON-RPC request from a byte slice. Pass WithAllowJSONRPC1 to accept JSON-RPC 1.0 requests; without it, decoding is strict JSON-RPC 2.0.

func DecodeRequestOrBatch

func DecodeRequestOrBatch(
	data []byte, opts ...DecodeOption,
) (reqs []*Request, isBatch bool, err error)

DecodeRequestOrBatch attempts to parse either a single request or a batch of requests. - For single requests: returns slice with one element - For batch requests: returns slice with multiple elements

func NewBatchNotification

func NewBatchNotification(methods []string, params []any) ([]*Request, error)

NewBatchNotification creates a batch of notifications (requests without IDs).

func NewBatchRequest

func NewBatchRequest(methods []string, params []any) ([]*Request, error)

NewBatchRequest creates a batch of JSON-RPC requests from methods and params. Each request receives an auto-generated ID.

Example

ExampleNewBatchRequest demonstrates creating batch requests with different param types.

methods := []string{"sum", "subtract", "getUser"}
params := []any{
	[]any{1, 2, 3},            // positional params for sum
	[]any{10, 5},              // positional params for subtract
	map[string]any{"id": 123}, // named params for getUser
}

reqs, err := NewBatchRequest(methods, params)
if err != nil {
	fmt.Printf("err: %v\n", err)
	return
}

for i, req := range reqs {
	fmt.Printf("Request %d: %s\n", i, req.Method)
}
Output:
Request 0: sum
Request 1: subtract
Request 2: getUser

func NewNotification

func NewNotification(method string, params any) *Request

NewNotification creates a JSON-RPC 2.0 notification (request without ID).

func NewRequest

func NewRequest(method string, params any) *Request

NewRequest creates a JSON-RPC 2.0 request with an auto-generated ID.

func NewRequestWithID

func NewRequestWithID(method string, params any, id any) *Request

NewRequestWithID creates a JSON-RPC 2.0 request with a specific ID.

func (*Request) IDString

func (r *Request) IDString() string

IDString returns the ID as a string.

func (*Request) IsEmpty

func (r *Request) IsEmpty() bool

IsEmpty returns whether the Request is empty. A request is considered empty if the method field is empty.

func (*Request) IsNotification

func (r *Request) IsNotification() bool

IsNotification returns true if this is a notification (no ID expected).

func (*Request) MarshalJSON

func (r *Request) MarshalJSON() ([]byte, error)

MarshalJSON marshals the Request to a JSON byte slice.

func (*Request) String

func (r *Request) String() string

String returns a string representation of the JSON-RPC request. Note: implements the fmt.Stringer interface.

func (*Request) UnmarshalJSON

func (r *Request) UnmarshalJSON(data []byte) error

UnmarshalJSON unmarshals a JSON-RPC request from a JSON byte slice. It enforces strict JSON-RPC 2.0; use DecodeRequest with WithAllowJSONRPC1 to accept 1.0 requests.

func (*Request) UnmarshalParams

func (r *Request) UnmarshalParams(dst any) error

UnmarshalParams decodes the Params field into the provided destination pointer. This is a convenience method for unmarshaling structured parameters.

Example

ExampleRequest_UnmarshalParams demonstrates unmarshaling params into a struct.

// Create a request with structured params
req := NewRequest("createUser", map[string]any{
	"name":  "Bob",
	"email": "bob@example.com",
	"age":   30,
})

// Define target struct
type UserParams struct {
	Name  string `json:"name"`
	Email string `json:"email"`
	Age   int    `json:"age"`
}

// Unmarshal params into struct
var params UserParams
if err := req.UnmarshalParams(&params); err != nil {
	fmt.Printf("err: %v\n", err)
	return
}

fmt.Printf("Name: %s, Email: %s, Age: %d\n", params.Name, params.Email, params.Age)
Output:
Name: Bob, Email: bob@example.com, Age: 30

func (*Request) Validate

func (r *Request) Validate() error

Validate checks if the JSON-RPC request conforms to the JSON-RPC specification.

type Response

type Response struct {
	// contains filtered or unexported fields
}

Response is a struct for JSON-RPC responses conforming to the JSON-RPC 2.0 specification. Response instances are immutable after decoding and safe for concurrent reads.

The Response type uses lazy unmarshaling for the id and error fields to optimize performance.

func DecodeBatchResponse

func DecodeBatchResponse(data []byte, opts ...DecodeOption) ([]*Response, error)

DecodeBatchResponse parses a JSON-RPC batch response from a byte slice. Returns an error if: - Input is not a JSON array - Array is empty - Any element fails to parse as a valid Response

func DecodeBatchResponseFromReader

func DecodeBatchResponseFromReader(
	r io.Reader, expectedSize int, opts ...DecodeOption,
) ([]*Response, error)

DecodeBatchResponseFromReader parses a JSON-RPC batch response from an io.Reader.

func DecodeResponse

func DecodeResponse(data []byte, opts ...DecodeOption) (*Response, error)

DecodeResponse parses and returns a new Response from a byte slice. Pass WithAllowJSONRPC1 to accept JSON-RPC 1.0 responses; without it, decoding is strict JSON-RPC 2.0.

func DecodeResponseFromReader

func DecodeResponseFromReader(
	r io.Reader, expectedSize int, opts ...DecodeOption,
) (*Response, error)

DecodeResponseFromReader parses and returns a new Response from an io.Reader. expectedSize is optional and used for internal buffer sizing; pass 0 if unknown.

func DecodeResponseOrBatch

func DecodeResponseOrBatch(
	data []byte, opts ...DecodeOption,
) (resps []*Response, isBatch bool, err error)

DecodeResponseOrBatch attempts to parse either a single response or a batch of responses. - For single responses: returns slice with one element - For batch responses: returns slice with multiple elements

func NewErrorResponse

func NewErrorResponse(id any, err *Error) *Response

NewErrorResponse creates a JSON-RPC 2.0 error response.

func NewResponse

func NewResponse(id any, result any) (*Response, error)

NewResponse creates a JSON-RPC 2.0 response with a result.

func NewResponseFromRaw

func NewResponseFromRaw(id any, rawResult json.RawMessage) (*Response, error)

NewResponseFromRaw creates a JSON-RPC 2.0 response with a raw result.

func (*Response) Clone added in v0.8.0

func (r *Response) Clone() (*Response, error)

Clone creates a copy of the response with minimal shared state.

Deep copies:

  • rawID, rawError, result byte slices
  • Error struct

Shallow copies type any fields:

  • id field
  • Error.Data field

Not copied:

  • AST cache (clone starts with empty cache)
  • Sync primitives (fresh Once and Mutex created)

func (*Response) Equals

func (r *Response) Equals(other *Response) bool

Equals compares the contents of this Response with another Response.

func (*Response) Err

func (r *Response) Err() *Error

Err returns the error from the response. If there is no error, nil is returned.

func (*Response) Free added in v0.8.0

func (r *Response) Free()

Free releases memory-retaining fields. Only use after consuming the response.

func (*Response) IDOrNil

func (r *Response) IDOrNil() any

IDOrNil returns the unmarshaled ID, or nil if unmarshaling fails.

func (*Response) IDString

func (r *Response) IDString() string

IDString returns the ID as a string.

func (*Response) IsEmpty

func (r *Response) IsEmpty() bool

IsEmpty returns whether the Response can be considered empty.

A JSON-RPC response is considered empty when BOTH the error and result are empty:

  • Result is empty if: empty byte slice, null, empty string (""), empty array ([]), empty object ({}), or hex zero value ("0x")
  • Error is empty if: nil, or has both code=0 and message=""

func (*Response) MarshalJSON

func (r *Response) MarshalJSON() ([]byte, error)

MarshalJSON serializes the Response into a JSON-RPC 2.0 compliant byte slice.

The method prioritizes parsed fields (id, err) over their raw counterparts (rawID, rawError) when both are present. If only raw fields exist, they are used directly to avoid unnecessary re-marshaling.

func (*Response) PeekBytesByPath added in v0.8.0

func (r *Response) PeekBytesByPath(path ...any) ([]byte, error)

PeekBytesByPath returns raw JSON bytes for a nested field without unmarshaling the entire result. This is useful when you want to extract a sub-object or array and unmarshal it separately.

Example usage to extract a nested transaction object:

txBytes, err := response.PeekBytesByPath("transaction")

The AST node is lazily built on first call and cached for subsequent calls, making repeated field access very efficient.

func (*Response) PeekStringByPath added in v0.8.0

func (r *Response) PeekStringByPath(path ...any) (string, error)

PeekStringByPath traverses the result JSON using sonic's AST to extract a string field without unmarshaling the entire result. This is valuable for large responses where you only need to access specific nested fields.

The path is specified as a sequence of keys for nested objects. An example using multiple arguments:

from, err := response.PeekStringByPath("transaction", "from")

The AST node is lazily built on first call and cached for subsequent calls, making repeated field access very efficient.

func (*Response) RawResult

func (r *Response) RawResult() json.RawMessage

RawResult returns the raw JSON-encoded result bytes. Note: for string results, this includes the JSON quotes (e.g., "result" not result).

func (*Response) Size added in v0.8.0

func (r *Response) Size() int

Size returns the approximate serialized size of the response in bytes.

func (*Response) String

func (r *Response) String() string

String returns a string representation of the JSON-RPC response.

func (*Response) Unmarshal added in v0.7.2

func (r *Response) Unmarshal(dst any) error

Unmarshal deserializes the entire Response into a custom struct.

Note: It performs a marshal-then-unmarshal round trip, making it less efficient than using individual getter methods (IDOrNil(), Err(), RawResult(), UnmarshalResult()).

func (*Response) UnmarshalError

func (r *Response) UnmarshalError() error

UnmarshalError deserializes the raw error bytes into the err field of the Response.

Example

ExampleResponse_UnmarshalError demonstrates handling error responses.

jsonData := []byte(
	`{"jsonrpc":"2.0","id":1,"error":{"code":-32601,"message":"Method not found"}}`,
)
resp, err := DecodeResponse(jsonData)
if err != nil {
	fmt.Printf("Decode error: %v\n", err)
	return
}

// For responses with errors, the error is automatically unmarshaled during decode
// Check if response has an error
if resp.Err() != nil {
	fmt.Printf("RPC Error %d: %s\n", resp.Err().Code, resp.Err().Message)
}
Output:
RPC Error -32601: Method not found

func (*Response) UnmarshalJSON

func (r *Response) UnmarshalJSON(data []byte) error

UnmarshalJSON deserializes JSON-RPC 2.0 response data into the Response. Returns an error if the data is invalid JSON, has an incorrect JSON-RPC version, contains both result and error fields, or contains neither.

func (*Response) UnmarshalResult

func (r *Response) UnmarshalResult(dst any) error

UnmarshalResult deserializes the raw Result field into the provided destination.

Example

ExampleResponse_UnmarshalResult demonstrates unmarshaling result into different types.

// Simulate decoding a response with a structured result
jsonData := []byte(`{"jsonrpc":"2.0","id":1,"result":{"balance":1000,"currency":"USD"}}`)
resp, err := DecodeResponse(jsonData)
if err != nil {
	fmt.Printf("err: %v\n", err)
	return
}

// Define target struct
type BalanceResult struct {
	Balance  int    `json:"balance"`
	Currency string `json:"currency"`
}

// Unmarshal result
var result BalanceResult
if err := resp.UnmarshalResult(&result); err != nil {
	fmt.Printf("err: %v\n", err)
	return
}

fmt.Printf("Balance: %d %s\n", result.Balance, result.Currency)
Output:
Balance: 1000 USD
Example (Primitives)

ExampleResponse_UnmarshalResult_primitives demonstrates unmarshaling primitive results.

// String result
jsonData := []byte(`{"jsonrpc":"2.0","id":1,"result":"success"}`)
resp, _ := DecodeResponse(jsonData)

var strResult string
if err := resp.UnmarshalResult(&strResult); err != nil {
	fmt.Printf("err: %v\n", err)
	return
}
fmt.Printf("String: %s\n", strResult)

// Number result
jsonData = []byte(`{"jsonrpc":"2.0","id":2,"result":42}`)
resp, _ = DecodeResponse(jsonData)

var intResult int
if err := resp.UnmarshalResult(&intResult); err != nil {
	fmt.Printf("err: %v\n", err)
	return
}
fmt.Printf("Number: %d\n", intResult)

// Boolean result
jsonData = []byte(`{"jsonrpc":"2.0","id":3,"result":true}`)
resp, _ = DecodeResponse(jsonData)

var boolResult bool
if err := resp.UnmarshalResult(&boolResult); err != nil {
	fmt.Printf("err: %v\n", err)
	return
}
fmt.Printf("Boolean: %t\n", boolResult)
Output:
String: success
Number: 42
Boolean: true

func (*Response) Validate

func (r *Response) Validate() error

Validate checks if the Response conforms to the JSON-RPC 2.0 specification. The version field must be exactly "2.0"; this strict check is used on the encode path.

func (*Response) Version

func (r *Response) Version() string

Version returns the JSON-RPC protocol version.

func (*Response) WithID added in v0.8.1

func (r *Response) WithID(newID any) (*Response, error)

WithID returns a cloned response with the supplied ID while leaving the original untouched. The clone keeps cached payload slices so callers avoid repeated marshaling work.

func (*Response) WriteTo added in v0.8.0

func (r *Response) WriteTo(w io.Writer) (n int64, err error)

WriteTo implements io.WriterTo for efficient streaming serialization without buffering the entire response in memory. This is beneficial for large responses as it significantly reduces memory pressure.

Jump to

Keyboard shortcuts

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