JSON-RPC Request Builder — Kotlin source
Build valid JSON-RPC 2.0 requests, notifications, success responses, and error responses, plus batch arrays. Validate message structure.
This is the Kotlin implementation — the same logic the interactive tool runs, in a shareable, citable form.
// json-rpc-builder — Kotlin port: JSON-RPC 2.0 message builder + structural validation.
data class Outcome(val ok: Boolean, val json: String, val error: String? = null)
/** Sentinel telling "argument absent" apart from a legitimate JSON null. */
private val UNSET = Any()
fun standardError(code: Int): String = when (code) {
-32700 -> "Parse error"; -32600 -> "Invalid Request"; -32601 -> "Method not found"
-32602 -> "Invalid params"; -32603 -> "Internal error"; -32000 -> "Server error"
else -> "Error"
}
/** Minimal JSON writer — insertion-ordered Map, List, String, Number, Boolean, null. */
fun toJson(v: Any?): String = when (v) {
null -> "null"
is String -> "\"" + v.replace("\\", "\\\\").replace("\"", "\\\"") + "\""
is Number, is Boolean -> v.toString()
is List<*> -> v.joinToString(",", "[", "]") { toJson(it) }
is Map<*, *> -> v.entries.joinToString(",", "{", "}") { "${toJson(it.key)}:${toJson(it.value)}" }
else -> throw IllegalArgumentException("unsupported: ${v::class}")
}
private fun base(method: String) = linkedMapOf<String, Any?>("jsonrpc" to "2.0", "method" to method)
fun buildRequest(method: String?, params: Any? = UNSET, id: Any? = 1): Outcome {
if (method.isNullOrEmpty()) return Outcome(false, "", "method must be a non-empty string")
val obj = base(method)
if (params !== UNSET) obj["params"] = params
obj["id"] = id
return Outcome(true, toJson(obj))
}
// A notification is a request without an id: fire-and-forget, no reply.
fun buildNotification(method: String?, params: Any? = UNSET): Outcome {
if (method.isNullOrEmpty()) return Outcome(false, "", "method must be a non-empty string")
val obj = base(method)
if (params !== UNSET) obj["params"] = params
return Outcome(true, toJson(obj))
}
fun buildSuccessResponse(id: Any?, result: Any?): Outcome {
val obj = linkedMapOf<String, Any?>("jsonrpc" to "2.0", "result" to result, "id" to id)
return Outcome(true, toJson(obj))
}
fun buildErrorResponse(id: Any?, code: Int, message: String? = null, data: Any? = UNSET): Outcome {
val error = linkedMapOf<String, Any?>("code" to code, "message" to (message ?: standardError(code)))
if (data !== UNSET) error["data"] = data
val obj = linkedMapOf<String, Any?>("jsonrpc" to "2.0", "error" to error, "id" to id)
return Outcome(true, toJson(obj))
}
// Per spec §6 a batch is a non-empty array sent in one round-trip.
fun buildBatch(messages: Any?): Outcome {
if (messages !is List<*> || messages.isEmpty())
return Outcome(false, "", "batch must be a non-empty array")
return Outcome(true, toJson(messages))
}
/** Lightweight shape check — reports every problem, not just the first. */
fun validateRpc(obj: Any?): Pair<Boolean, List<String>> {
if (obj !is Map<*, *>) return false to listOf("Not an object.")
val errors = buildList {
if (obj["jsonrpc"] != "2.0") add("jsonrpc must be \"2.0\".")
if ("method" in obj && obj["method"] !is String) add("method must be a string.")
if ("result" in obj && "error" in obj) add("cannot have both result and error.")
if (listOf("method", "result", "error").none { it in obj })
add("must have method, result, or error.")
}
return (errors.isEmpty()) to errors
}
fun main() {
println(buildRequest("getBalance", listOf("0x1234"), 1).json)
println(buildNotification("blockHeader").json)
println(buildSuccessResponse(1, "0x1bc16d674ec80000").json)
println(buildErrorResponse(1, -32601).json)
println(validateRpc(mapOf("jsonrpc" to "1.0")))
}
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 →