Advanced Parsing

This example parses a user profile from camelCase JSON, including fields that may be missing and a field with a default value. It also parses HTTP headers to show how Roc’s type-directed parsing works with formats other than JSON.


User := {
	user_name : Str,
	nickname ?: Str,
	email : Try(Str, [Missing]),
	role : [Restricted, Admin] ?? Restricted,
}.{
	parser_for : _
}

parse_user : Str -> Try(User, [InvalidJson(Str), MissingRequiredField(Str)])
parse_user = Json.parser_camel()

parse_headers : Str -> Try({ content_length : U64, x_auth_token : Try(Str, [Missing]) }, [BadHeader, MissingRequiredField(Str)])
parse_headers = Encoding.HttpHeader.parser_for()

expect {
	user = parse_user(
		\\{"userName": "Sam"}
	)?
	user.user_name == "Sam" and user.email == Err(Missing) and (user.?nickname ?? "anonymous") == "anonymous" and user.role == Restricted
}
expect {
	user = parse_user(
		\\{"userName": "Sam", "email": "sam@example.com"}
	)?
	user.email == Ok("sam@example.com")
}
expect {
	user = parse_user(
		\\{"userName": "Sam", "nickname": "Sammy"}
	)?
	user.?nickname == Ok("Sammy")
}
expect {
	user = parse_user(
		\\{"userName": "Sam", "role": "Admin"}
	)?
	user.role == Admin
}
expect parse_user("{}").is_err()
expect {
	headers = parse_headers("Content-Length: 42\r\n\r\n")?
	headers.content_length == 42 and headers.x_auth_token == Err(Missing)
}

The user profile demonstrates three ways to handle a missing field:

Fields such as user_name remain required. Unlike the fallback in user.?nickname ?? "anonymous", which only supplies a value when reading the optional field, the role default becomes part of the parsed record.

Field defaults belong to nominal records, so User uses := rather than the type-alias syntax :. Nominal types also need to opt into derived parsing: parser_for : _ asks Roc to generate the parser for this record.

parser_camel() connects the JSON field userName to Roc’s user_name. Because we define the parser at the top level, it is assembled at compile time and is ready to parse input at runtime.

The HTTP header example uses the same approach: we describe the record we want, and the format’s parser fills it in.

Output

Run this from the directory that has main.roc in it:

$ roc test main.roc

All (6) tests passed in 101.4 ms.

Validated with Roc nightly-2026-10-29-a0a9fc2.