jsongame-developmentunitygodotsave-files

JSON in Game Development — Save Files, Level Data, and Game State

·8 min read·Advanced

Everywhere except the hot path

Game engines are famously performance-obsessed, which makes it easy to assume JSON has no place in one — and inside the actual render/physics loop, that's usually correct. But nearly everything *around* that loop — save files, level data, dialogue trees, config, mod support — is exactly the kind of infrequently-read, human-editable, tooling-friendly data JSON is good at, which is why it shows up constantly in game codebases despite the performance reputation.

Save files

A save file is read once at load and written once on save — not a hot path — which makes JSON's overhead irrelevant and its debuggability genuinely valuable: a corrupted or unexpected save state is far easier to diagnose by opening the file in a text editor than by attaching a hex viewer to a binary blob.

json
{
  "player": {
    "level": 14,
    "health": 82,
    "inventory": [
      { "itemId": "sword_iron", "quantity": 1 },
      { "itemId": "potion_health", "quantity": 3 }
    ],
    "position": { "x": 412.5, "y": -18.2, "map": "forest_01" }
  },
  "flags": { "metWizard": true, "bridgeRepaired": false }
}

Unity's JsonUtility and Newtonsoft.Json (via the Json.NET package) both serialize a C# save-data class directly to and from this shape; Godot's built-in JSON singleton does the same for GDScript. Versioning matters more here than almost anywhere else — add a "saveVersion": 3 field from day one, since a save format that outlives several game updates will eventually need to migrate old saves forward, and a version field is what makes that migration logic possible instead of guessing from the shape of the data itself.

Level and tilemap data

Tiled, the most widely used 2D level editor, exports maps as either its own XML format or JSON — and the JSON export has become the de facto standard for custom engine integrations precisely because it's trivial to parse in any language without a dedicated XML library:

json
{
  "width": 20,
  "height": 15,
  "tilewidth": 32,
  "layers": [
    { "name": "ground", "data": [1, 1, 1, 2, 2, 1, 1, 3, ...] }
  ]
}

The data array here is a flat list of tile IDs, one per grid cell — simple enough that a custom engine can write its own 20-line parser rather than depending on a full Tiled-format library, which is common for smaller or performance-sensitive engines that want tight control over how tile data loads.

Dialogue trees and narrative data

Branching dialogue is naturally tree/graph-shaped data, and writers on a team are rarely the same people writing engine code — a JSON (or JSON-adjacent, like Ink's compiled JSON output) dialogue format lets narrative designers iterate without touching a build:

json
{
  "id": "wizard_intro",
  "text": "You've come far, traveler.",
  "choices": [
    { "text": "Who are you?", "next": "wizard_backstory" },
    { "text": "I need your help.", "next": "wizard_quest_offer" }
  ]
}

Several dialogue tools (Yarn Spinner, Ink) compile their own authoring syntax down to JSON specifically so the runtime-side parsing logic in the engine stays simple and language-agnostic, while writers work in a friendlier custom syntax upstream of that compilation step.

Where JSON is the wrong choice

Anything in the render or physics loop. Parsing text every frame is wasted CPU that a binary format (or better, data already loaded into typed structs/arrays in memory) avoids entirely — JSON has no place inside a loop that runs 60+ times a second.

Large asset payloads — meshes, textures, audio. These are inherently binary data; encoding a texture as a JSON array of pixel values would be enormously larger and slower to parse than the binary image formats built for exactly this.

Anti-cheat-sensitive save data. A save file a player can trivially open in a text editor and hand-edit ("gold": 999999) is a real problem for any game with online leaderboards or competitive elements — some studios encrypt or checksum JSON save files specifically to detect tampering, or move to a binary format with less casual editability as a deterrent (not a real security boundary, just raising the bar past "open in Notepad").

Frequently asked questions

JSON for anything single-player where debuggability matters and file size is a non-issue (which covers the large majority of indie and mid-size games); a binary format if you need tamper-resistance for competitive/leaderboard integrity, or if save files are large enough (procedurally generated worlds, voxel data) that JSON's text overhead becomes a real load-time cost.

Unity's JsonUtility is fast but limited — no support for dictionaries, nullable types are awkward, and it silently ignores fields it doesn't understand, which can mask bugs. Most non-trivial Unity projects use Newtonsoft.Json (Json.NET) instead for its far more complete feature set. Godot's built-in JSON class parses into Godot's native Dictionary/Array types directly and works well for typical game-data shapes without an extra package.

For very large worlds, plain JSON's text parsing overhead becomes a real load-time cost — this is where a chunked, partially-binary, or streaming approach (loading only nearby chunks, in a more compact per-chunk format) typically replaces a single monolithic JSON file, even in games that use JSON everywhere else.

Yes, and this is one of its strongest use cases — a JSON-based data format for items, recipes, or entity definitions lets modders edit or add content with a text editor and no compiler, which is exactly why so many moddable games (especially in the survival/crafting genre) expose their core data tables as editable JSON files.

Try JSONKit — JSON Developer Tools

Format, validate, convert and diff JSON. Instant, accurate and 100% private.