Skip to content

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 →