Skip to content

JSON-RPC Request Builder — Swift source

Build valid JSON-RPC 2.0 requests, notifications, success responses, and error responses, plus batch arrays. Validate message structure.

This is the Swift implementation — the same logic the interactive tool runs, in a shareable, citable form.

// json-rpc-builder — Swift port: JSON-RPC 2.0 message builder + structural validation.

import Foundation

let jsonrpcVersion = "2.0"

struct Outcome { let ok: Bool; let json: String; let error: String? }

func standardError(_ code: Int) -> String {
    [-32700: "Parse error", -32600: "Invalid Request", -32601: "Method not found",
     -32602: "Invalid params", -32603: "Internal error", -32000: "Server error"][code] ?? "Error"
}

// [String: Any] + JSONSerialization is the stdlib way to hold arbitrary JSON.
// (Key order in the output is unspecified; JSON-RPC itself does not mandate one.)
func stringify(_ obj: Any, pretty: Bool = false) throws -> String {
    let data = try JSONSerialization.data(withJSONObject: obj, options: pretty ? [.prettyPrinted] : [])
    return String(decoding: data, as: UTF8.self)
}

// `hasParams` tells "absent" apart from a real JSON null, mirroring the TS sentinel.
func buildRequest(_ method: String, params: Any? = nil, hasParams: Bool = false,
                  id: Any = 1) throws -> Outcome {
    guard !method.isEmpty else {
        return Outcome(ok: false, json: "", error: "method must be a non-empty string")
    }
    var obj: [String: Any] = ["jsonrpc": jsonrpcVersion, "method": method]
    if hasParams { obj["params"] = params }
    obj["id"] = id
    return Outcome(ok: true, json: try stringify(obj), error: nil)
}

// A notification is a request without an id: fire-and-forget, no reply.
func buildNotification(_ method: String, params: Any? = nil,
                       hasParams: Bool = false) throws -> Outcome {
    guard !method.isEmpty else {
        return Outcome(ok: false, json: "", error: "method must be a non-empty string")
    }
    var obj: [String: Any] = ["jsonrpc": jsonrpcVersion, "method": method]
    if hasParams { obj["params"] = params }
    return Outcome(ok: true, json: try stringify(obj), error: nil)
}

func buildSuccessResponse(id: Any, result: Any) throws -> Outcome {
    let obj: [String: Any] = ["jsonrpc": jsonrpcVersion, "result": result, "id": id]
    return Outcome(ok: true, json: try stringify(obj), error: nil)
}

func buildErrorResponse(id: Any, code: Int, message: String? = nil,
                        data: Any? = nil, hasData: Bool = false) throws -> Outcome {
    var err: [String: Any] = ["code": code, "message": message ?? standardError(code)]
    if hasData { err["data"] = data }
    let obj: [String: Any] = ["jsonrpc": jsonrpcVersion, "error": err, "id": id]
    return Outcome(ok: true, json: try stringify(obj), error: nil)
}

/// Lightweight shape check — reports every problem, not just the first.
/// A parsed JSON null is NSNull (not nil), so present-but-null fields count as present.
func validateRpc(_ obj: Any?) -> (valid: Bool, errors: [String]) {
    guard let o = obj as? [String: Any] else { return (false, ["Not an object."]) }
    var errors: [String] = []
    if o["jsonrpc"] as? String != jsonrpcVersion { errors.append("jsonrpc must be \"2.0\".") }
    if let m = o["method"], !(m is String) { errors.append("method must be a string.") }
    if o["result"] != nil && o["error"] != nil {
        errors.append("cannot have both result and error.")
    }
    if o["method"] == nil && o["result"] == nil && o["error"] == nil {
        errors.append("must have method, result, or error.")
    }
    return (errors.isEmpty, errors)
}

let req = try buildRequest("getBalance", params: ["0x1234"], hasParams: true)
print(req.json)
print(try buildNotification("blockHeader").json)
print(try buildSuccessResponse(id: 1, result: "0x1bc16d674ec80000").json)
print(try buildErrorResponse(id: 1, code: -32601).json)
let verdict = validateRpc(["jsonrpc": "1.0"])
print("\(verdict.valid): \(verdict.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 →