JSON-RPC Request Builder — C source
Build valid JSON-RPC 2.0 requests, notifications, success responses, and error responses, plus batch arrays. Validate message structure.
This is the C implementation — the same logic the interactive tool runs, in a shareable, citable form.
/* json-rpc-builder — C port: JSON-RPC 2.0 message builder + structural validation. */
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#define JSONRPC_VERSION "2.0"
typedef struct { bool ok; char json[512]; const char *error; } Outcome;
/* Messages for the error codes the JSON-RPC 2.0 spec reserves (§5.1). */
static const char *standard_error(int code)
{
switch (code) {
case -32700: return "Parse error"; case -32600: return "Invalid Request";
case -32601: return "Method not found"; case -32602: return "Invalid params";
case -32603: return "Internal error"; case -32000: return "Server error";
default: return "Error";
}
}
/* Params/id/data arrive as pre-encoded JSON fragments, so no allocator or JSON
* library is needed — snprintf into the caller's Outcome (escaping elided). */
static Outcome build_request(const char *method, const char *params, const char *id)
{
if (!method || !*method) return (Outcome){ .ok = false, .error = "method must be a non-empty string" };
char p[128] = "";
if (params) snprintf(p, sizeof p, ",\"params\":%s", params);
Outcome o = { .ok = true };
snprintf(o.json, sizeof o.json,
"{\"jsonrpc\":\"" JSONRPC_VERSION "\",\"method\":\"%s\"%s,\"id\":%s}", method, p, id);
return o;
}
/* A notification is a request without an id: fire-and-forget, no reply. */
static Outcome build_notification(const char *method, const char *params)
{
if (!method || !*method) return (Outcome){ .ok = false, .error = "method must be a non-empty string" };
char p[128] = "";
if (params) snprintf(p, sizeof p, ",\"params\":%s", params);
Outcome o = { .ok = true };
snprintf(o.json, sizeof o.json,
"{\"jsonrpc\":\"" JSONRPC_VERSION "\",\"method\":\"%s\"%s}", method, p);
return o;
}
static Outcome build_error_response(const char *id, int code, const char *message, const char *data)
{
char d[128] = "";
Outcome o = { .ok = true };
if (data) snprintf(d, sizeof d, ",\"data\":%s", data);
snprintf(o.json, sizeof o.json,
"{\"jsonrpc\":\"" JSONRPC_VERSION "\",\"error\":{\"code\":%d,\"message\":\"%s\"%s},\"id\":%s}",
code, message ? message : standard_error(code), d, id);
return o;
}
/* Structural check of one parsed message. Which fields exist drives the verdict;
* `method` is typed as a string by the struct itself. */
typedef struct {
const char *jsonrpc; /* NULL = absent */
bool has_method, has_result, has_error;
} MessageShape;
static int validate(const MessageShape *m, const char *errors[3])
{
int n = 0;
if (!m->jsonrpc || strcmp(m->jsonrpc, JSONRPC_VERSION) != 0)
errors[n++] = "jsonrpc must be \"2.0\".";
if (m->has_result && m->has_error)
errors[n++] = "cannot have both result and error.";
if (!m->has_method && !m->has_result && !m->has_error)
errors[n++] = "must have method, result, or error.";
return n;
}
int main(void)
{
const char *errors[3];
printf("%s\n", build_request("getBalance", "[\"0x1234\"]", "1").json);
printf("%s\n", build_notification("blockHeader", NULL).json);
printf("%s\n", build_error_response("1", -32601, NULL, NULL).json);
int count = validate(&(MessageShape){ "1.0", false, false, false }, errors);
for (int i = 0; i < count; i++) printf("error: %s\n", errors[i]);
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →