Quick answer: Array.Load() in Construct 3 expects the c2array format — {"c2array":true,"size":[w,h,d],"data":[[[...]]]} — not raw JSON. If the marker or 3D shape is missing the array stays zero-sized with no error. Wrap your JSON in the c2array envelope before calling Load, run the call from the AJAX On completed trigger, and strip any BOM characters from the response.

You build a level data file in JSON because every other tool in your pipeline understands JSON. You drop an AJAX request into your Construct 3 game to fetch it, pipe the response into Array.Load(AJAX.LastData), and run the project. The array is empty. Width zero, height zero, depth zero, no errors in the console. You stare at the JSON in a text editor — it parses fine in any other tool you have. You stare at the AJAX response — it arrives intact. The Load action is silently rejecting your data because it is the wrong format, and Construct 3’s array system does not believe in error messages.

The Symptom

You feed JSON into Array.Load() and the array remains empty. Specific manifestations:

Array.Width returns 0. After the Load action runs, your array is zero-sized in all dimensions. Iterating over For each X element never enters the loop body. There is no error event, no console warning, no exception in the browser DevTools.

Works once, fails the next reload. The first time you load a stored array (saved with Array.AsJSON), it works. Loading a hand-written JSON file fails. The c2array format that Construct 3 emits via AsJSON is not the format you produce by hand.

Loads partial data. One row appears but the rest does not. Your JSON is a flat array, but Construct 3 interprets the first element as a single value at (0, 0, 0) and stops because it cannot infer the dimensions.

Race condition on start of layout. Array loads on start of layout work in the editor preview but fail when deployed because the AJAX request has not completed by the time Load runs. The empty array is the empty initial state, not a parsing failure.

What Causes This

The c2array format. Construct 3 (and Construct 2 before it, hence the c2 prefix) saves arrays as a JSON object with three required fields: c2array: true as a marker, size: [width, height, depth] as a length-3 array, and data as a 3D nested array of values. Even if your data is conceptually 1D, you must nest it three times: [[[1, 2, 3]]] for a width-3 1D array. Array.Load() checks for the marker first — if it is missing or false, the load is treated as a no-op and the array keeps its previous (or initial empty) state.

BOM characters. Some text editors and some web servers emit a UTF-8 byte order mark (the bytes EF BB BF) at the start of files. JSON parsers in browsers handle BOMs differently; Construct 3’s c2array parser does not strip them, so the leading invisible character makes the JSON fail to parse before the c2array marker is even checked.

Trailing whitespace or trimming. If the response is delivered as binary and converted to a string with the wrong encoding, characters at the end may be lost. AJAX responses also sometimes get trimmed by intermediate proxies that strip the final newline. Either can leave the JSON syntactically invalid.

Action ordering. The biggest source of false positives. Calling AJAX.Request followed immediately by Array.Load(AJAX.LastData) in the same event runs Load before the request completes — AJAX is asynchronous, but events execute synchronously top to bottom. AJAX.LastData is the empty string at that moment. The array loads “successfully” with empty input.

The Fix

Step 1: Convert your JSON to c2array format. Use the Browser plugin’s Execute JavaScript action with a small wrapper that takes plain JSON and returns the c2array envelope. Store the result in a global string variable that Array.Load can then consume.

// In Browser.Execute JavaScript, after AJAX completes:
try {
  const raw = runtime.objects.AJAX.getFirstInstance()
                .lastData;

  // Strip a leading BOM if present
  const clean = raw.replace(/^/, "");

  // Parse plain JSON — expecting an array of values
  const data = JSON.parse(clean);

  // Wrap in the c2array envelope (1D → nest 3 deep)
  const wrapped = {
    "c2array": true,
    "size": [data.length, 1, 1],
    "data": [[ data.map(v => [v]) ]]
  };

  // Hand the c2array string to a global string var
  runtime.globalVars.LevelDataC2 =
    JSON.stringify(wrapped);
} catch (e) {
  console.error("c2array wrap failed:", e);
}

For 2D data (a grid), size is [width, height, 1] and data is [col][row][0]. For 3D data, all three dimensions matter and data is fully nested. The shape of data must exactly match what size claims, or you get partial loads.

Step 2: Load only after AJAX completes. Move the Array.Load action into the AJAX object’s On completed trigger filtered by the tag of your request. This is the only place where AJAX.LastData is guaranteed to contain the response.

// Event flow (pseudo-code, mirror in your event sheet):

// On start of layout:
//   AJAX.Request URL "data/level1.json" tag "level1"

// On AJAX "level1" completed:
//   Browser.Execute JavaScript (the wrapping snippet above)
//   LevelArray.Load(LevelDataC2)

// On AJAX "level1" failed:
//   Browser.Log "level load failed"
//   Show error UI

// Optional sanity check after Load:
if (LevelArray.width() === 0) {
  console.warn(
    "Array still empty after Load — check c2array format"
  );
}

Always handle the failure case too. AJAX failures should not silently leave you with an empty array that the rest of your game treats as “the data loaded but the level was empty”.

Validating the JSON Before You Ship

The most reliable way to avoid c2array surprises is to keep your tooling on the c2array format from the beginning. Save a sample array from Construct 3 itself using Array.AsJSON, inspect the structure, and produce your data files in that exact shape. Most level editors export plain JSON, so you will need a small Node.js or Python script in your build pipeline that wraps the editor output into c2array format before it ships.

Add a unit test (or a manual smoke test) that loads each data file at boot and asserts the array dimensions match expectations. The cost of three lines of validation is far less than the cost of debugging an empty array on a player’s machine where you cannot attach DevTools. A simple if Array.Width = 0 -> Browser.Log("Asset failed: " & AssetName) event catches the entire class of bugs.

Strip BOMs Aggressively

If you cannot control how the JSON file is produced — for example, if a designer is editing it in Notepad on Windows — assume a BOM may be present. Always strip  from the beginning of any string you parse with JSON.parse. The cost is one regex per load. You should also strip CR characters before LF when running on Windows; some pipelines convert \n to \r\n inside JSON strings, which makes the JSON technically valid but breaks string comparisons later.

“Construct 3 arrays use a custom JSON shape, not standard JSON. Pretend the c2array envelope is mandatory because, for Array.Load, it is.”

Related Issues

If your AJAX request itself fails on certain platforms, check CORS headers on your asset host — iOS standalone PWAs are particularly strict about cross-origin requests. If the array loads but values come out as strings instead of numbers, your wrapper is missing a numeric cast in the data array.

If Array.Width returns 0, it is almost always either a missing c2array envelope or a Load that ran before AJAX finished.