JSON
Nostos provides comprehensive JSON support for parsing, serialization, and typed deserialization. The fromJson[T] builtin makes it easy to convert JSON directly to your custom types with full type safety.
Parsing JSON
Use jsonParse to parse a JSON string into a Json value.
Tip: Use single-quoted strings for JSON to avoid escaping double quotes: '{"name": "Alice"}' instead of "{\"name\": \"Alice\"}"
# Import from stdlib
use stdlib.json.{jsonParse, jsonStringify, Json}
main() = {
# Parse a JSON string (single quotes make it cleaner!)
json: Json = jsonParse('{"name": "Alice", "age": 30}')
# The Json type is a variant:
# Null | Bool(Bool) | Number(Float) | String(String)
# | Array(List[Json]) | Object(List[(String, Json)])
println(json)
}
Note: In the REPL, stdlib functions are auto-imported so you can use jsonParse directly. In script files, use the use statement to import them.
Typed Deserialization with fromJson
The fromJson[T](json) builtin converts JSON to typed Nostos values. This is the recommended way to work with JSON data.
use stdlib.json.{jsonParse}
# Define your types
type Person = { name: String, age: Int }
main() = {
# Parse JSON string
json: Json = jsonParse('{"name": "Alice", "age": 30}')
# Convert to typed value (fromJson is a builtin, no import needed)
person: Person = fromJson[Person](json)
println(person.name) # "Alice"
println(person.age) # 30
}
Note: jsonToType[T] is an alias for fromJson[T] and works identically.
Supported Types
fromJson supports all Nostos types including numeric types, records, variants, tuples, and nested structures.
# All numeric types work
type Sizes = {
i8: Int8, i16: Int16, i32: Int32, i64: Int,
u8: UInt8, u16: UInt16, u32: UInt32, u64: UInt64,
f32: Float32, f64: Float
}
# Records with any field types
type Config = { name: String, enabled: Bool, value: Float }
# Nested types
type Address = { city: String, zip: Int }
type Person = { name: String, address: Address }
# Tuples (represented as JSON arrays)
type Pair = { data: (Int, String) }
main() = {
# Nested record example
json: Json = jsonParse('{"name": "Bob", "address": {"city": "Oslo", "zip": 1234}}')
person: Person = fromJson[Person](json)
println(person.address.city) # "Oslo"
# Tuple example
json2: Json = jsonParse('{"data": [42, "hello"]}')
pair: Pair = fromJson[Pair](json2)
println(pair.data) # (42, hello)
}
Variants (Sum Types)
Variants use a JSON object with the constructor name as the key. Fields use _0, _1, etc. for positional values. Unit variants (no fields) use null or empty object.
type Result = Ok(Int) | Err(String)
getValue(Ok(n)) = n
getValue(Err(_)) = 0
main() = {
# Ok(42) is represented as: {"Ok": {"_0": 42}}
json: Json = jsonParse('{"Ok": {"_0": 42}}')
result: Result = fromJson[Result](json)
println(getValue(result)) # 42
# Err("fail") is: {"Err": {"_0": "something went wrong"}}
json2: Json = jsonParse('{"Err": {"_0": "something went wrong"}}')
result2: Result = fromJson[Result](json2)
println(tagOf(result2)) # "Err"
}
# Multi-field variants use _0, _1, _2...:
type Coord = Point(Int, Int)
# Point(10, 20) is: {"Point": {"_0": 10, "_1": 20}}
# Unit variants (no payload) use null or empty object:
type Status = Active | Pending | Done
# Active is: {"Active": null} or {"Active": {}}
Error Handling
fromJson throws catchable exceptions when the JSON doesn't match the expected type.
type Person = { name: String, age: Int }
main() = {
# Missing required field
json: Json = jsonParse('{"name": "Alice"}')
try {
person: Person = fromJson[Person](json)
"success: " ++ person.name
} catch { e -> "Error: " ++ e }
# Returns: "Error: Missing field: age"
}
Common Errors
Missing field: <name>- Required field not in JSONUnknown constructor: <name>- Variant constructor doesn't existUnknown type: <name>- Type not definedExpected Json Object, found <type>- Wrong JSON structure
Round-Trip: Value to JSON and Back
Use reflect() to convert a typed value to JSON, and fromJson to parse it back.
type User = { id: Int, name: String, active: Bool }
main() = {
# Create a user (positional construction)
user = User(1, "Bob", true)
# Convert to JSON string
jsonStr = jsonStringify(user)
println("JSON: " ++ jsonStr)
# Parse back to typed value
parsed: Json = jsonParse(jsonStr)
user2: User = fromJson[User](parsed)
println("Equal: " ++ show(user == user2)) # true
}
Practical Example: Parsing API Responses
Combine HTTP requests with fromJson for type-safe API consumption.
type IpResponse = { origin: String }
main() = {
(status, resp): (String, HttpResponse) = Http.get("https://httpbin.org/ip")
if status == "ok" then {
json: Json = jsonParse(resp.body)
ipResp: IpResponse = fromJson[IpResponse](json)
println("Your IP: " ++ ipResp.origin)
} else {
println("Failed to fetch IP")
}
}