mjsonrpc 1.0
A lightweight JSON-RPC 2.0 message parser and generator based on cJSON
Loading...
Searching...
No Matches
mjsonrpc - A JSON-RPC 2.0 Message Parser and Generator Based on cJSON

GitHub Actions Workflow Status GitHub License GitHub commit activity GitHub top language GitHub repo size GitHub Downloads (all assets, latest release)

[ English | 中文 ]

Introduction

This project is lightweight, has minimal dependencies, and can be integrated into various communication methods (TCP, UDP, message queues, etc.). It is simple to use (with only a few functional APIs) and has good performance (using hash-based indexing instead of polling all methods). It also supports batch calls (JSON Array), automatic generation of corresponding error messages or custom error messages based on requests, customizable memory management hooks, notification requests, and more.

Features

  • Lightweight & Minimal Dependencies: Only depends on cJSON
  • Hash-based Method Indexing: Fast method lookup using double hashing algorithm
  • Batch Requests: Support for JSON Array batch calls
  • Customizable Memory Management: User-defined malloc/free/strdup hooks
  • Thread-Safe: Thread-local storage for memory hooks
  • POSIX Array Params: Support for both object and array parameters
  • Method Enumeration: Query registered methods at runtime
  • Error Logging: Optional error logging hooks for debugging

How to Use

Simply add the project source files (mjsonrpc.c, mjsonrpc.h) and the cJSON library to your own project and compile them together. Alternatively, you can use CMake to build and install:

cmake -B build && cmake --build build
sudo cmake --install build

Examples

Basic Usage

#include "mjsonrpc.h"
#include <stdio.h>
#include <stdlib.h>
// Define a simple JSON-RPC method
cJSON *hello_world(mjrpc_func_ctx_t *context, cJSON *params, cJSON *id) {
cJSON *result = cJSON_CreateString("Hello, World!");
return result;
}
int main() {
// Initialize mjrpc_handle_t
// Add a method
mjrpc_add_method(handle, hello_world, "hello", NULL);
// Construct a JSON-RPC request
const char *json_request = "{\"jsonrpc\":\"2.0\",\"method\":\"hello\",\"id\":1}";
// Process the request
int result;
char *json_response = mjrpc_process_str(handle, json_request, &result);
// Check return code
if (result != MJRPC_RET_OK) {
printf("Error processing request: %d\n", result);
}
// Check response string
if (json_response) {
printf("Response: %s\n", json_response);
free(json_response);
}
// Cleanup
return 0;
}
int mjrpc_destroy_handle(mjrpc_handle_t *handle)
Destroy a JSON-RPC handle and free all associated memory.
mjrpc_handle_t * mjrpc_create_handle(size_t initial_capacity)
Create a new JSON-RPC handle.
int mjrpc_add_method(mjrpc_handle_t *handle, mjrpc_func function_pointer, const char *method_name, void *arg2func)
Register a new RPC method.
char * mjrpc_process_str(const mjrpc_handle_t *handle, const char *request_str, int *ret_code)
Process a JSON-RPC request string.
A lightweight JSON-RPC 2.0 message parser and generator based on cJSON.
@ MJRPC_RET_OK
Operation completed successfully.
Definition mjsonrpc.h:87
Context structure passed to RPC method callback functions.
Definition mjsonrpc.h:121
Main handle structure for managing RPC methods.
Definition mjsonrpc.h:177

Method with Parameters

cJSON *add(mjrpc_func_ctx_t *ctx, cJSON *params, cJSON *id) {
int a = 0, b = 0;
cJSON *a_item = cJSON_GetObjectItem(params, "a");
cJSON *b_item = cJSON_GetObjectItem(params, "b");
if (cJSON_IsNumber(a_item)) a = a_item->valueint;
if (cJSON_IsNumber(b_item)) b = b_item->valueint;
return cJSON_CreateNumber(a + b);
}
int main() {
mjrpc_add_method(h, add, "add", NULL);
// Request with object parameters
const char *req = "{\"jsonrpc\":\"2.0\",\"method\":\"add\","
"\"params\":{\"a\":5,\"b\":3},\"id\":1}";
int code;
char *resp = mjrpc_process_str(h, req, &code);
// Response: {"jsonrpc":"2.0","result":8,"id":1}
printf("Response: %s\n", resp);
free(resp);
return 0;
}

Batch Requests

cJSON *multiply(mjrpc_func_ctx_t *ctx, cJSON *params, cJSON *id) {
int a = cJSON_GetArrayItem(params, 0)->valueint;
int b = cJSON_GetArrayItem(params, 1)->valueint;
return cJSON_CreateNumber(a * b);
}
int main() {
mjrpc_add_method(h, multiply, "mul", NULL);
// Batch request with multiple calls
const char *batch_req = "["
"{\"jsonrpc\":\"2.0\",\"method\":\"mul\",\"params\":[3,4],\"id\":1},"
"{\"jsonrpc\":\"2.0\",\"method\":\"mul\",\"params\":[5,6],\"id\":2}"
"]";
int code;
char *resp = mjrpc_process_str(h, batch_req, &code);
// Response: [{"jsonrpc":"2.0","result":12,"id":1},{"jsonrpc":"2.0","result":30,"id":2}]
printf("Batch Response: %s\n", resp);
free(resp);
return 0;
}

Custom Error Handling

cJSON *validate_age(mjrpc_func_ctx_t *ctx, cJSON *params, cJSON *id) {
cJSON *age = cJSON_GetObjectItem(params, "age");
if (!cJSON_IsNumber(age) || age->valueint < 0 || age->valueint > 150) {
ctx->error_code = -32001; // Custom error code
ctx->error_message = strdup("Invalid age: must be 0-150");
return NULL;
}
return cJSON_CreateString("Age validated");
}
int32_t error_code
Error code to be set by the method implementation (0 = no error)
Definition mjsonrpc.h:126
char * error_message
Error message to be set by the method implementation (will be freed automatically)
Definition mjsonrpc.h:130

Custom Memory Management

void* my_malloc(size_t size) {
printf("Allocating %zu bytes\n", size);
return malloc(size);
}
void my_free(void* ptr) {
printf("Freeing %p\n", ptr);
free(ptr);
}
char* my_strdup(const char* str) {
char* dup = my_malloc(strlen(str) + 1);
if (dup) strcpy(dup, str);
return dup;
}
int main() {
// Set custom memory hooks
mjrpc_set_memory_hooks(my_malloc, my_free, my_strdup);
// ... use handle ...
// Reset to default
mjrpc_set_memory_hooks(NULL, NULL, NULL);
return 0;
}
int mjrpc_set_memory_hooks(mjrpc_malloc_func malloc_func, mjrpc_free_func free_func, mjrpc_strdup_func strdup_func)
Set custom memory management functions.

Error Logging

void my_error_logger(const char* msg, int code) {
fprintf(stderr, "[MJSONRPC ERROR] %s (code: %d)\n", msg, code);
}
int main() {
// Enable error logging
mjrpc_set_error_log_hook(my_error_logger);
// ... errors will be logged automatically ...
// Disable error logging
return 0;
}
int mjrpc_set_error_log_hook(mjrpc_error_log_func error_log_func)
Set custom error logging function.

Performance

Hash Table Performance Comparison

Operation Quadratic Probing Double Hashing (new)
Avg probes (low load) ~1.5 ~1.2
Avg probes (high load) ~4.0 ~2.0
Worst case O(n) O(n)

Method Registration Throughput

Methods Add (ops/sec) Delete (ops/sec) Lookup (ops/sec)
100 ~500K ~600K ~800K
1000 ~450K ~550K ~750K
10000 ~400K ~500K ~700K

Tested on Intel i7, 3.2GHz, single-threaded

FAQ

Q: Is mjsonrpc thread-safe?

A: The library uses thread-local storage for memory hooks, making it safe to use from multiple threads. However, you should protect shared mjrpc_handle_t instances with your own synchronization (mutexes) if accessed from multiple threads concurrently.

Q: How do I handle notification requests (no response)?

A: Notification requests are those without an id field. The library processes them normally but returns NULL for the response. Check the return code for MJRPC_RET_OK_NOTIFICATION.

const char *notif = "{\"jsonrpc\":\"2.0\",\"method\":\"log\",\"params\":{\"msg\":\"hello\"}}";
int code;
char *resp = mjrpc_process_str(h, notif, &code);
printf("Notification processed (no response)\n");
}
@ MJRPC_RET_OK_NOTIFICATION
Operation completed successfully (notification request)
Definition mjsonrpc.h:90

Q: Can I use array parameters instead of object parameters?

A: Yes! Both object and array parameters are supported:

// Array parameters
const char *req = "{\"jsonrpc\":\"2.0\",\"method\":\"add\",\"params\":[1,2],\"id\":1}";

Q: How do I enumerate all registered methods?

A: Use mjrpc_enum_methods() to get an array of method names:

cJSON *methods = mjrpc_enum_methods(handle, NULL, NULL);
// methods is a JSON array of method name strings
cJSON_Delete(methods);
int mjrpc_enum_methods(const mjrpc_handle_t *handle, void(*callback)(const char *method_name, void *arg, void *user_data), void *user_data)
Enumerate all registered methods.

Q: What happens if the hash table needs to resize?

A: The library automatically resizes the hash table when the load factor exceeds 0.75. This is transparent to the user. The resize operation uses double hashing for rehashing, which provides better distribution.

Q: How do I clean up resources properly?

A: Always call mjrpc_destroy_handle() when done. This frees all internally allocated memory. Any cJSON objects returned to you should be freed with cJSON_Delete() or free() as documented.

References