> For the complete documentation index, see [llms.txt](https://docs.allout.game/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.allout.game/data-and-persistence/json.md).

# JSON Serialization

For saving more advanced data structures, you can serialize them to JSON and store them in the Save system.

CSL can serialize certain data structures into JSON for you. This is most useful when you want to save a “bundle” of related fields (like player progress, upgrades, unlocks, etc) without managing lots of separate save keys.

Under the hood, this uses the Save system's JSON APIs:

* `Save.set_json(player, key, value)`
* `Save.try_get_json(player, key, out) -> bool`

{% hint style="info" %}
JSON save/load is **per-player** (it takes a `player`). Game-wide save currently supports strings + ints only.
{% endhint %}

### What gets saved?

Only fields marked with `@ao_serialize` are included in the JSON.

```go
Player_Progress :: class {
    @ao_serialize version: s64 = 1;

    @ao_serialize xp: s64;
    @ao_serialize level: s64 = 1;

    @ao_serialize unlocked_skins: [..]string;
    @ao_serialize equipped_weapon: Item_Instance;
}
```

### Saving JSON

Save the whole structure under one key:

```go
// Note: Only @ao_serialize fields are written
Save.set_json(player, "progress", ref progress);
```

In Save JSON, `Item_Instance` fields and derived item fields persist as selections from any of that player's persistent inventories. Transferring an item to another inventory invalidates its references, even for the same player. Saving an item outside that player's persistent inventories raises an error. Missing or invalidated references and derived-type mismatches load as `null`. Create named inventories before loading the record. Standalone `JSON.serialize` does not provide this item-reference behavior.

### Loading JSON (with defaults)

`Save.try_get_json` returns `false` if the key is missing or the JSON string is malformed. Allocate class data before loading because the false path does not allocate it. Missing fields keep their class defaults, and unknown fields are ignored.

```go
load_progress :: proc(player: Player) -> Player_Progress {
    progress := new(Player_Progress);

    if !Save.try_get_json(player, "progress", ref progress) {
        // New player (or malformed JSON) already has the class defaults.
    }

    return progress;
}
```

{% hint style="warning" %}
Changing a stored field to an incompatible type is invalid input and can fail loudly. Use a new field or save key when the old value cannot be converted safely.
{% endhint %}

### Versioning & migrations

If you expect your JSON schema to change, include a `version` field in the structure and migrate after load.

Best practices for staying compatible:

* **Prefer adding new fields** with sensible class defaults.
* Keep a version field when a new value must be derived from old data.
* If you need a hard break, consider saving under a **new key** (e.g. `"progress_v2"`) and keeping a fallback loader.

Migration Example:

```go
load_progress_and_migrate :: proc(player: Player) -> Player_Progress {
    progress := new(Player_Progress);

    if !Save.try_get_json(player, "progress", ref progress) {
        // The class defaults already describe a new player.
    }

    // Migrate forward
    if progress.version < 2 {
        progress.version = 2;
        // Example: v2 introduced unlocked_skins (initialize it)
        progress.unlocked_skins = .{};
    }

    // Write back the migrated version
    Save.set_json(player, "progress", ref progress);
    return progress;
}
```

### Fixed arrays

Fixed-array input is bounded by the destination:

* Extra JSON items are ignored.
* Missing items leave the remaining destination elements unchanged.

### When to use JSON vs simple keys

* Use **simple keys** (`Save.set_int`, `Save.set_string`, etc) for a handful of values you read/write often.
* Use **JSON** when you want to store a cohesive structure (progress, loadouts, unlocks) and keep your save logic centralized.

### Standalone JSON API

You can also serialize/deserialize JSON independently of the Save system (e.g. for logging, networking, or custom storage):

```go
JSON :: struct {
    serialize       :: proc(obj: ref $T) -> string;
    try_deserialize :: proc(json: string, out_value: ref $T) -> bool;
}
```
