# JSON Support

### JSON Support

JSON is a text format for storing structured data.

JSON is common because it is simple, readable, and widely supported. Web APIs use it. Configuration files use it. Command-line tools use it. Databases and message queues often accept it.

A JSON value can be:

```text
null
true
false
123
"hello"
[1, 2, 3]
{"name": "Alice", "age": 30}
```

JSON looks simple, but parsing it correctly still matters. A parser must handle strings, numbers, arrays, objects, escaping, whitespace, invalid input, and memory allocation.

#### A Small JSON Object

Here is a small JSON document:

```json
{
  "name": "Alice",
  "age": 30,
  "debug": true
}
```

It contains an object with three fields.

The field names are strings.

The values have different types:

```text
name  -> string
age   -> number
debug -> boolean
```

In Zig, you usually parse JSON into either a generic JSON representation or a typed struct.

#### A Matching Zig Struct

For known data, a struct is usually best.

```zig
const Config = struct {
    name: []const u8,
    age: u32,
    debug: bool,
};
```

This says exactly what the program expects.

`name` is text.

`age` is an unsigned integer.

`debug` is a boolean.

A parser can then fill this struct from JSON.

#### Why JSON Parsing Can Fail

JSON parsing can fail for many reasons.

The text may not be valid JSON.

A field may be missing.

A field may have the wrong type.

A number may be too large for the destination type.

A string may contain invalid escaping.

The input may be too deeply nested.

The allocator may fail.

So JSON parsing uses errors. That fits Zig’s normal style.

#### Generic JSON vs Typed JSON

There are two common ways to parse JSON.

Generic parsing gives you a tree of JSON values. You inspect fields manually.

Typed parsing parses directly into a Zig type.

Generic parsing is flexible. It is useful when the shape of the JSON is not known ahead of time.

Typed parsing is stricter. It is useful when you know the expected shape.

For application configuration, typed parsing is usually clearer.

#### A Conceptual Typed Parse

The exact `std.json` APIs can change across Zig versions, so check your installed docs with:

```bash
zig std
```

The conceptual shape is:

```zig
const parsed = try std.json.parseFromSlice(
    Config,
    allocator,
    json_text,
    .{},
);
defer parsed.deinit();

const config = parsed.value;
```

The parts are important.

`Config` is the Zig type you want.

`allocator` is used for any memory the parser needs.

`json_text` is the input.

`parsed.deinit()` frees parser-owned memory.

`parsed.value` is the parsed result.

#### A Complete Typed JSON Example

```zig
const std = @import("std");

const Config = struct {
    name: []const u8,
    age: u32,
    debug: bool,
};

pub fn main() !void {
    const allocator = std.heap.page_allocator;

    const json_text =
        \\{
        \\  "name": "Alice",
        \\  "age": 30,
        \\  "debug": true
        \\}
    ;

    const parsed = try std.json.parseFromSlice(
        Config,
        allocator,
        json_text,
        .{},
    );
    defer parsed.deinit();

    const config = parsed.value;

    std.debug.print("name = {s}\n", .{config.name});
    std.debug.print("age = {}\n", .{config.age});
    std.debug.print("debug = {}\n", .{config.debug});
}
```

Output:

```text
name = Alice
age = 30
debug = true
```

The input is text. The output is typed Zig data.

That is the main benefit.

#### Memory Ownership

JSON strings often point into parsed memory.

That is why this matters:

```zig
defer parsed.deinit();
```

Do not keep references to parsed string fields after `parsed.deinit()` has run.

This is wrong:

```zig
const name = parsed.value.name;
parsed.deinit();

std.debug.print("{s}\n", .{name});
```

After `deinit`, `name` may point to memory that has been freed.

Use parsed values only while the parsed object is alive, or copy the data into memory you own separately.

#### Missing Fields

Suppose the JSON is:

```json
{
  "name": "Alice",
  "debug": true
}
```

The `age` field is missing.

If your struct requires `age`, parsing should fail.

This is good. It prevents partially valid configuration from being accepted silently.

When a field is optional, express that in the type:

```zig
const Config = struct {
    name: []const u8,
    age: ?u32,
    debug: bool,
};
```

The type `?u32` means the age may be absent or null.

Now your program must handle both cases.

```zig
if (config.age) |age| {
    std.debug.print("age = {}\n", .{age});
} else {
    std.debug.print("age missing\n", .{});
}
```

#### Default Values

Sometimes a missing field should use a default.

```zig
const Config = struct {
    name: []const u8,
    age: u32 = 0,
    debug: bool = false,
};
```

Now `age` and `debug` have defaults.

Use defaults deliberately. They are useful for configuration, but they can also hide mistakes if used carelessly.

A good rule is:

Required fields should not have defaults.

Optional behavior may have defaults.

#### Arrays

JSON arrays map naturally to Zig slices or arrays.

JSON:

```json
{
  "ports": [8000, 8001, 8002]
}
```

Zig struct:

```zig
const Config = struct {
    ports: []const u16,
};
```

After parsing, you can loop over the slice:

```zig
for (config.ports) |port| {
    std.debug.print("port = {}\n", .{port});
}
```

Again, the slice belongs to the parsed JSON object. Keep the parsed object alive while using the slice.

#### Nested Objects

JSON objects can contain other objects.

```json
{
  "server": {
    "host": "127.0.0.1",
    "port": 8080
  }
}
```

Use nested structs:

```zig
const Server = struct {
    host: []const u8,
    port: u16,
};

const Config = struct {
    server: Server,
};
```

This is one of the cleanest parts of typed JSON parsing. The JSON shape becomes a Zig type.

#### Writing JSON

Programs often need to produce JSON too.

The conceptual shape is:

```zig
try std.json.stringify(value, .{}, writer);
```

You give it a Zig value, options, and an output writer.

For example, a value like:

```zig
const config = Config{
    .name = "Alice",
    .age = 30,
    .debug = true,
};
```

can be written as JSON.

Depending on your output target, the writer may write to stdout, a file, or a memory buffer.

#### JSON Is Text

JSON is text, usually encoded as UTF-8.

That means you can store it in a `[]const u8`.

```zig
const json_text: []const u8 = "{\"name\":\"Alice\"}";
```

The escapes make this hard to read. Zig multiline strings are often nicer:

```zig
const json_text =
    \\{
    \\  "name": "Alice"
    \\}
;
```

This is useful for tests and examples.

#### Unknown Fields

Some parsers reject unknown fields by default. Others allow them depending on options.

Suppose your struct is:

```zig
const Config = struct {
    name: []const u8,
};
```

and the JSON is:

```json
{
  "name": "Alice",
  "extra": 123
}
```

You must decide whether this should be accepted.

For strict configuration, rejecting unknown fields is often better. It catches typos.

For external APIs, allowing unknown fields may be better. APIs often add fields over time.

This is a design decision, not just a parser setting.

#### JSON Numbers

JSON has one number syntax.

Zig has many numeric types:

```zig
u8
u16
u32
u64
i32
i64
f32
f64
```

When parsing JSON into a typed struct, the parser must check whether the number fits the requested Zig type.

This should fail:

```json
{
  "port": 999999
}
```

if the destination type is:

```zig
port: u16
```

because `u16` cannot store that value.

This is good. It keeps invalid data out of your program.

#### A Small Config Loader

A typical JSON config loader has this shape:

```zig
const std = @import("std");

const Config = struct {
    host: []const u8,
    port: u16,
    debug: bool = false,
};

fn loadConfig(
    allocator: std.mem.Allocator,
    text: []const u8,
) !std.json.Parsed(Config) {
    return std.json.parseFromSlice(Config, allocator, text, .{});
}

pub fn main() !void {
    const allocator = std.heap.page_allocator;

    const text =
        \\{
        \\  "host": "127.0.0.1",
        \\  "port": 8080
        \\}
    ;

    const parsed = try loadConfig(allocator, text);
    defer parsed.deinit();

    const config = parsed.value;

    std.debug.print("host = {s}\n", .{config.host});
    std.debug.print("port = {}\n", .{config.port});
    std.debug.print("debug = {}\n", .{config.debug});
}
```

The default value for `debug` is used because the JSON does not contain that field.

#### JSON From a File

A real program often reads JSON from a file, then parses it.

Conceptually:

```zig
const file_text = try readWholeFile(allocator, "config.json");
defer allocator.free(file_text);

const parsed = try std.json.parseFromSlice(
    Config,
    allocator,
    file_text,
    .{},
);
defer parsed.deinit();
```

This combines the ideas from earlier sections:

read bytes from a file

treat them as JSON text

parse them into a typed value

free allocated memory

#### Common Mistakes

Do not assume JSON input is valid.

Do not ignore parse errors.

Do not keep string slices after freeing the parsed object.

Do not use `f64` for values that are really integers such as ports, counts, and IDs.

Do not accept unknown fields in configuration unless that is intentional.

Do not parse huge untrusted JSON without limits.

Do not use JSON when you need compact binary data or exact schema evolution control.

#### The Core Pattern

Define a type:

```zig
const Config = struct {
    name: []const u8,
    port: u16,
    debug: bool = false,
};
```

Parse JSON text:

```zig
const parsed = try std.json.parseFromSlice(
    Config,
    allocator,
    json_text,
    .{},
);
defer parsed.deinit();
```

Use the value:

```zig
const config = parsed.value;
```

Keep the parsed object alive while using slices inside it.

#### What You Should Remember

JSON is structured text.

Zig can parse JSON into typed data.

Typed parsing is usually best when you know the expected shape.

Parsing can fail, so handle errors.

JSON strings and arrays may depend on parser-owned memory.

Use `defer parsed.deinit()` to release that memory.

Use optional fields and defaults deliberately.

Treat JSON as external input.

A good JSON parser boundary turns untrusted text into validated Zig values.

