Modules

Json

moduleSource
macOSLinuxWindowswasm

#import "Extensions/Json";

Typed JSON for jaic programs, in the spirit of Rust's serde_json. Encoders and decoders are generated from your types at compile time, so there is no reflection walk at run time. A jaic extension, not official Jai: other Jai compilers have no such module, and it may change between jaic releases. See docs/extensions/json.md.

ExampleOpen in playground ↗
#import "Extensions/Json";

User :: struct @json(rename_all=camelCase) {
    id: int;
    display_name: string;                 // "displayName"
    email: string;      @json(skip_serializing_if=empty)
    tags: [..] string;
}

ada := User.{id = 7, display_name = "Ada"};
text := to_json(ada);                     // pretty: to_json(ada, pretty = true)
user, error := from_json(User, text);     // error.kind == .NONE on success
value, value_error := from_json(Json_Value, text);    // when the shape is unknown
free_json(*user);                         // also for a Json_Value and a Json_Error

Procedures#

General#

json_path_textsince 0.27.0
(error: Json_Error) -> string

The error's path in jsonpath form, as jsonb_path_query takes it: $ for the document itself, $.address.tags[2], and a key that is not a plain identifier quoted: $."we ird". Allocated with the temporary allocator.

free_json3 overloads
(error: *Json_Error)

Releases the message and path of an error and clears it. Part of the free_json set.

(value: $T, pretty := false) -> string

Encodes value. Output is compact unless pretty (two-space indent). The result comes from context.allocator.

from_json2 overloads
($T: Type, source: Json_Source, options := Json_Options.{}) -> (T, Json_Error)

Decodes a T from a source in pieces. The pieces are gathered in one buffer (the decoder wants the whole document, and its strings are copied out of it, so the buffer is freed before this returns) and then decoded exactly as from_json(T, text) would, so a document split at any byte gives the same value or the same error. A source that fails gives a .READ error with the source's text; a document longer than options.max_size is a .LIMIT error and the rest is not read.

(w: *Json_Writer, value: *$T)

Writes the JSON of the value value points to. This and json_decode are what with= handlers call for the parts they do not encode themselves.

Value#

(value: float64) -> Json_Value

NaN and infinities have no JSON spelling and become null.

(object: *Json_Value, key: string, value: Json_Value)

Sets a member of an object, replacing the last member with that key; object takes ownership of value and copies key.

(value: *Json_Value) -> int

How many elements an array has, or members an object has; 0 for anything else.

(value: *Json_Value) -> (s64, bool)

A number that is written as an integer and fits. False for anything else.

(value: *Json_Value) -> (float64, bool)

Any number, rounded to the nearest float64. False if it is not a number or is too large.

Writer#

(builder: *String_Builder, text: string)

Appends text as a JSON string with its quotes. Control characters, quotes and backslashes are escaped; bytes that are not valid UTF-8 become U+FFFD so the output is always valid JSON.

(w: *Json_Writer, first: *bool, quoted_key: string)

Starts a member: the separator, then the key. quoted_key is the key with its quotes and escapes already applied (the generated code does that at compile time).

(w: *Json_Writer, first: *bool, quoted_key: string, first_form: string, next_form: string)

json_key with the compact forms prebuilt at compile time: first_form is "key": and next_form is ,"key":, so compact output costs one append.

Reader#

(r: *Json_Reader, key: string)

Say the value being read is at key / index of its parent. The generated decoders call these while an error travels up, so the error's path reads address.tags[2].

(r: *Json_Reader, first: *bool) -> (string, Object_Status)

Reads up to the next member of the object: its key (valid until the next read) and the colon after it, or the closing brace. first is true before the first call.

(r: *Json_Reader) -> (string, bool)

A string value, copied into new memory from context.allocator (with borrow, a slice of the input when it has no escapes).

(r: *Json_Reader) -> (string, bool)

A string value without copying: a view of the input, or of the reader's scratch space when it has escapes. Valid until the next read; copy it to keep it.

json_keep_keysince 0.27.0
(r: *Json_Reader, key: string) -> string

Keeps a key from json_read_name: a slice of the input with borrow (unless it had escapes and lives in the scratch space), else a copy.

(r: *Json_Reader, out: *s64, low: s64, high: s64, type_name: string) -> bool

Reads an integer into out, which must fit between low and high. type_name appears in the message when it does not.

Query#

($T: Type, pairs: [] Query_Pair, options := Json_Options.{}) -> (T, Json_Error)

Decodes a T from pairs. A key that repeats gives an array for an array field, and is a "duplicate field" error for any other. Keys no field has are ignored unless T is marked deny_unknown_fields. Everything allocated (strings, arrays) comes from context.allocator and is released by free_json, as for from_json.

Search :: struct @json(default) { q: string; limit := 20; tags: [..] string; }

// The pairs of "?q=jai&tags=a&tags=b".
pairs := Query_Pair.[.{"q", "jai"}, .{"tags", "a"}, .{"tags", "b"}];
search, error := from_query(Search, pairs);

assert(error.kind == .NONE);
assert(search.q == "jai");
assert(search.limit == 20);
assert(search.tags.count == 2);
↗
($T: Type, text: string, options := Json_Options.{}) -> (T, Json_Error)

Decodes a T from one piece of text: an integer, float, bool, string or enum. from_param(int, "42") is 42; from_param(Color, "red") the member red; a string is taken as it is.

Types#

:: struct

Why decoding failed and where. path names the value inside the document (tags[2], address.zip); line and column count from 1, the column in characters. message and path are allocated with context.allocator: free_json releases them.

Json_Presenceenumsince 0.27.0
:: enum

A field that distinguishes a key that is missing, a null and a value. As a struct member a missing key leaves .MISSING (never a missing-field error), null gives .NULL and any other value decodes into value and gives .SET. Encoding omits the key for .MISSING, writes null for .NULL and the value for .SET. Outside a struct member (the document, an array element) .MISSING encodes as null. free_json releases value when it is .SET.

:: struct

How the structural index fared since the program began, for tests and curiosity.

:: #type (data: *void, buffer: [] u8) -> s64, string

Where a document comes from when it is not in memory yet: fill puts the next piece of text in buffer and returns how many bytes it wrote (0 at the end), and a non-empty string when the source failed (what went wrong). data is handed to it as it is. Json does not know what the pieces come from; Io.read_json makes one from an Io.Reader.

:: struct

One JSON value. Only the fields of its kind are used: BOOL boolean NUMBER text, the number as written (exact for integers of any size; see json_as_s64) STRING text, the contents with escapes resolved ARRAY items OBJECT members, in document order (duplicate keys are kept; json_get finds the last) Everything a Json_Value owns comes from context.allocator; free_json releases it.

:: struct

Reads one JSON document. Every reading procedure returns false after recording an error in error; the first error wins, and callers must stop at once and return false themselves.

:: struct

One key=value of a query or a form, already percent-decoded.

Constants#

:: u64.[ 0xeef453d6923bd65a, 0x113faa2906a13b3f, // 5^-342 0x9558b4661b6565f8, 0x4ac7ca59a424c507, // 5^-341 0xbaaee17fa23ebf76, 0x5d79bcf00d2df649, // 5^-340 0xe9 ...
:: u64.[ 0x4f0cedc95a718dd4, 0x5b01e8b09aa0d1b5, // k = -324 0x7e7b160ef71c1621, 0x119ca780f767b5ee, // k = -323 0x652f44d8c5b011b4, 0x0e16ec672c52f7f2, // k = -32 ...