Skip to content

JSON & HTTP Examples

Onion provides built-in support for JSON and YAML parsing, HTTP requests, and deriving serializers for records.

Deriving JSON for Records

Use derive!(Json) on a record to automatically generate toJson and fromJson static methods.

record User(id: Int, name: String, email: String) derive!(Json)

val user = new User(1, "Alice", "alice@example.com")
val json = User::toJson(user)
println(json)

val parsed: User? = User::fromJson(json)
if parsed != null {
  println("name=" + parsed.name())
}

Output:

{"id":1,"name":"Alice","email":"alice@example.com"}

Parsing JSON Strings

For ad-hoc JSON, use the Json module directly.

val raw = "{\"count\": 42, \"items\": [\"a\", \"b\", \"c\"]}"
val map = Json::parse(raw) as Map
println("count=" + map.get("count"))

YAML Config Files

Records can also derive YAML support with derive!(Yaml).

record ServerConfig(host: String, port: Int, debug: Boolean) derive!(Yaml)

val yaml = "host: localhost\nport: 8080\ndebug: false\n"
val cfg: ServerConfig? = ServerConfig::fromYaml(yaml)
if cfg != null {
  println(cfg.host() + ":" + cfg.port())
}

HTTP GET Requests

Use the Http module for simple HTTP calls.

try {
  val response = Http::get("https://api.example.com/users")
  println("length=" + response.length())
} catch e: Exception {
  println("request failed: " + e.getMessage())
}

HTTP POST with JSON

Send JSON in a POST request:

val payload = "{\"name\":\"Alice\",\"age\":30}"
try {
  val response = Http::postJson("https://api.example.com/users", payload)
  println(response)
} catch e: Exception {
  println("request failed")
}

Complete Example: JSON API Client

JsonApiClient.on demonstrates record serialization, HTTP GET, and graceful fallback when the network is unavailable.

record User(id: Int, name: String, email: String) derive!(Json)

val userJson = "{\"id\":1,\"name\":\"Alice\",\"email\":\"alice@example.com\"}"
val user = User::fromJson(userJson)
if user != null {
  println("parsed " + user.name())
}

val url = "https://httpbin.org/get"
try {
  val response = Http::get(url)
  println("http status length=" + response.length())
} catch e: Exception {
  println("network request skipped")
}

// Round-trip
val bob = new User(42, "Bob", "bob@example.com")
val back = User::fromJson(User::toJson(bob))
if back != null {
  println("roundtrip=" + back.name())
}

Serving HTTP

MiniService.on answers requests instead of making them. Routing lives in select over the path, so a regex pattern binds its captured group as a value.

val server = Server::start("localhost", 8080)

server.handleAll((req) -> select req.path() {
  case re"/users/(\d+)" (id): Server::json("{\"id\": " + id + "}")
  case "/health":             Server::text("ok")
  else:                       Server::notFound()
})

IO::println("listening on " + server.port())
server.await()

Port 0 asks the OS for a free port and port() reports the one it chose, which is how to start a server in a test without picking a number and hoping. run/MiniWebService.on is a complete version that answers its own requests and exits.

Raw TCP

TcpClient.on speaks a protocol Http does not.

val conn = Net::connect("example.com", 80, 5000)
conn.writeLine("GET / HTTP/1.0")
conn.writeLine("Host: example.com")
conn.writeLine("")
IO::println(conn.readAll())
conn.close()

Accepting is the mirror image:

val listener = Net::listen("localhost", 0, 4)
val peer = listener.accept()
peer.writeLine("echo: " + peer.readLine())
peer.close()
listener.close()

URL Utilities

Build query strings and encode values:

val query = Http::buildQuery(["q", "onion lang", "limit", "10"])
val url = "https://api.example.com/search?" + query
println(url)

Next Steps