JSON

JSON.stringify and JSON.parse work exactly as they do in any JavaScript runtime. They are the standard way to persist structured data and talk to external APIs.

const playerData = {
  name: 'Notch',
  level: 50,
  inventory: ['diamond_sword', 'golden_apple'],
};

const json = JSON.stringify(playerData);
// {"name":"Notch","level":50,"inventory":["diamond_sword","golden_apple"]}

const parsed = JSON.parse(json);
log(parsed.name); // Notch

Always guard JSON.parse

Parsing untrusted or stored input can throw. Wrap it:

try {
  const data = JSON.parse(jsonString);
  applySettings(data);
} catch (e) {
  error('Failed to parse stored settings:', e.message);
}

Data that has been in your database since three schema versions ago counts as untrusted.

Java objects do not serialize

This is the one PixelScript-specific gotcha. JSON.stringify only understands JavaScript values. A Java object such as a Location, ItemStack or Player will not round-trip.

Pull out the primitives first:

// Wrong: produces something useless
JSON.stringify({ location: player.getLocation() });

// Right: extract what you actually need
JSON.stringify({
  world: player.getWorld().getName(),
  x: player.getLocation().getX(),
  y: player.getLocation().getY(),
  z: player.getLocation().getZ(),
});

The same applies to Java collections. Convert an ArrayList or HashMap to a JS array or object before serializing.

For locations specifically, DataFile has setLocation and getLocation helpers that handle the round-trip for you in YAML.

Other unsupported values behave as they do everywhere else in JavaScript: functions, undefined and symbols are dropped, and circular references throw. Convert Date objects with toISOString().

Storing JSON in the database

A JSON blob in a TEXT column is a perfectly reasonable way to store a bag of settings that you never query by field:

function savePlayerSettings(uuid, settings) {
  Scheduler.runAsync(() => {
    Sql.makeUpdate(
      'UPDATE player_data SET settings = ? WHERE uuid = ?',
      (stmt) => {
        stmt.setString(1, JSON.stringify(settings));
        stmt.setString(2, uuid);
      },
      (rows) => { if (rows === -1) error('Failed to save settings for', uuid); }
    );
  });
}

function loadPlayerSettings(uuid, callback) {
  Scheduler.runAsync(() => {
    Sql.makeQuery(
      'SELECT settings FROM player_data WHERE uuid = ?',
      (stmt) => stmt.setString(1, uuid),
      (rs) => {
        let settings = null;

        if (rs !== null && rs.next()) {
          try {
            settings = JSON.parse(rs.getString('settings'));
          } catch (e) {
            error('Corrupt settings blob for', uuid, e.message);
          }
        }

        Scheduler.runSync(() => callback(settings));
      }
    );
  });
}

If you find yourself wanting to filter or sort on a value inside the blob, promote it to a real column with Sql.addColumnIfNotExists.

Deep cloning

The usual trick works, with the usual caveat that it only survives JSON-representable values:

const clone = JSON.parse(JSON.stringify(original));
  • Database for persistence
  • HTTP fetch, whose response.asObject() parses JSON for you
  • Shared state for YAML config files, which are usually nicer for anything a human edits