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.

using System.Text.Json;
using System.Text.Json.Nodes;

var req = BuildRequest("getBalance", 1, JsonValue.Create(new[] { "0x1234" }), hasParams: true);
Console.WriteLine(req.Json);
Console.WriteLine(BuildNotification("blockHeader").Json);
Console.WriteLine(BuildSuccessResponse(1, "0x1bc16d674ec80000").Json);
Console.WriteLine(BuildErrorResponse(1, -32601).Json);
var (valid, problems) = Validate(JsonSerializer.Deserialize<JsonObject>(@"{""jsonrpc"":""1.0""}"));
Console.WriteLine($"{valid}: {string.Join("; ", problems)}");

record Outcome(bool Ok, string Json, string? Error = null);

static string? StandardError(int code) => code switch {
    -32700 => "Parse error", -32600 => "Invalid Request", -32601 => "Method not found",
    -32602 => "Invalid params", -32603 => "Internal error", -32000 => "Server error",
    _ => null,
};

// JsonObject preserves insertion order, so key order matches the TS lib.
static string Stringify(JsonNode node, int? indent = null) =>
    node.ToJsonString(indent is > 0
        ? new JsonSerializerOptions { WriteIndented = true }   // 2-space pretty print
        : null);

// `hasParams` tells "absent" apart from a real JSON null — the C# analog of the
// TS lib's unset sentinel (optional reference parameters cannot).
static Outcome BuildRequest(string method, JsonNode? id, JsonNode? @params = null,
                            bool hasParams = false, int? indent = null) {
    if (string.IsNullOrEmpty(method))
        return new Outcome(false, "", "method must be a non-empty string");
    var o = new JsonObject { ["jsonrpc"] = "2.0", ["method"] = method };
    if (hasParams) o["params"] = @params;
    o["id"] = id;
    return new Outcome(true, Stringify(o, indent));
}

// A notification is a request without an id: fire-and-forget, no reply.
static Outcome BuildNotification(string method, JsonNode? @params = null,
                                 bool hasParams = false, int? indent = null) {
    if (string.IsNullOrEmpty(method))
        return new Outcome(false, "", "method must be a non-empty string");
    var o = new JsonObject { ["jsonrpc"] = "2.0", ["method"] = method };
    if (hasParams) o["params"] = @params;
    return new Outcome(true, Stringify(o, indent));
}

static Outcome BuildSuccessResponse(JsonNode? id, JsonNode? result, int? indent = null) {
    var o = new JsonObject { ["jsonrpc"] = "2.0", ["result"] = result, ["id"] = id };
    return new Outcome(true, Stringify(o, indent));
}

static Outcome BuildErrorResponse(JsonNode? id, int code, string? message = null,
                                  JsonNode? data = null, bool hasData = false,
                                  int? indent = null) {
    var err = new JsonObject { ["code"] = code, ["message"] = message ?? StandardError(code) ?? "Error" };
    if (hasData) err["data"] = data;
    var o = new JsonObject { ["jsonrpc"] = "2.0", ["error"] = err, ["id"] = id };
    return new Outcome(true, Stringify(o, indent));
}

// Lightweight shape check — reports every problem, not just the first.
static (bool Valid, List<string> Errors) Validate(JsonObject? o) {
    if (o is null) return (false, new List<string> { "Not an object." });
    var errors = new List<string>();
    if (o["jsonrpc"]?.GetValue<string>() != "2.0") errors.Add("jsonrpc must be \"2.0\".");
    if (o.ContainsKey("method") && o["method"]!.GetValue<JsonElement>().ValueKind != JsonValueKind.String)
        errors.Add("method must be a string.");
    if (o.ContainsKey("result") && o.ContainsKey("error"))
        errors.Add("cannot have both result and error.");
    if (!o.ContainsKey("method") && !o.ContainsKey("result") && !o.ContainsKey("error"))
        errors.Add("must have method, result, or error.");
    return (errors.Count == 0, errors);
}

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 →