Skip to content

Basic Usage ​

Decorated classes ​

Any class marked @json can be parsed and serialized. Fields are matched to JSON keys by name.

ts
import { JSON } from "json-as";

@json
class Point {
  x: i32 = 0;
  y: i32 = 0;
}

JSON.stringify(new Point()); // '{"x":0,"y":0}'

const p = JSON.parse<Point>('{"x":3,"y":4}');
p.y; // 4

Give fields a default (x: i32 = 0) or mark them definitely-assigned (x!: i32). A default is used when the key is missing from the input.

Nested classes and arrays ​

Fields can be other @json classes, arrays of them, and arrays of primitives — nesting works to any depth.

ts
@json
class Path {
  name: string = "";
  points: Point[] = [];
}

const path = JSON.parse<Path>(
  '{"name":"a","points":[{"x":1,"y":2},{"x":3,"y":4}]}',
);
path.points[1].x; // 3

JSON.stringify(path); // '{"name":"a","points":[{"x":1,"y":2},{"x":3,"y":4}]}'

Renaming keys with @alias ​

When the JSON key can't be an AssemblyScript identifier (or just differs from your field name), map it with @alias:

ts
@json
class User {
  @alias("user_id") id: i32 = 0;
  name: string = "";
}

const u = new User();
u.id = 42;
u.name = "ada";
JSON.stringify(u); // '{"user_id":42,"name":"ada"}'

JSON.parse<User>('{"user_id":7,"name":"x"}').id; // 7

Optional fields ​

Three decorators drop a field from the output (parsing still accepts the key either way):

  • @omit — never serialized.
  • @omitnull() — skipped when the value is null.
  • @omitif(predicate) — skipped when the predicate (given the instance) returns true.
ts
@json
class Session {
  token: string = "";
  @omit secret: string = "";
}

@json
class Profile {
  name: string = "";
  @omitnull() bio: string | null = null;
}

@json
class Player {
  name: string = "";
  @omitif((self: Player) => self.score == 0) score: i32 = 0;
}
ts
const s = new Session();
s.token = "t";
s.secret = "hidden";
JSON.stringify(s); // '{"token":"t"}'  — secret never emitted

const pr = new Profile();
pr.name = "ada";
JSON.stringify(pr); // '{"name":"ada"}'  — bio is null, skipped
pr.bio = "hi";
JSON.stringify(pr); // '{"bio":"hi","name":"ada"}'

const pl = new Player();
pl.name = "z";
JSON.stringify(pl); // '{"name":"z"}'  — score is 0, skipped
pl.score = 9;
JSON.stringify(pl); // '{"score":9,"name":"z"}'

Key order

Conditionally-emitted fields (@omitnull / @omitif) are serialized before the always-present ones — note bio and score appear first above. The order is still valid JSON; object key order isn't significant.

@omitif also accepts a string expression evaluated against the instance, e.g. @omitif("this.score == 0").

Nullable primitives with JSON.Box ​

AssemblyScript can't express i32 | null (value types aren't nullable). Wrap the primitive in JSON.Box<T> to get a nullable number/bool:

ts
@json
class Account {
  name: string = "";
  balance: JSON.Box<i32> | null = null;
}

const a = JSON.parse<Account>('{"name":"a","balance":50}');
a.balance!.value; // 50

const b = JSON.parse<Account>('{"name":"a","balance":null}');
b.balance == null; // true

A Box<T> serializes as the bare value (or null), not as an object.

Reusing an instance ​

Both parse and stringify take an optional second argument to reuse an existing object or output string instead of allocating a fresh one — handy in hot loops over a fixed shape.

ts
const into = new Point();
JSON.parse<Point>('{"x":1,"y":2}', into); // fills `into` in place
into.x; // 1

let out = "";
out = JSON.stringify(new Point(), out); // reuses `out`'s buffer when it can

On a steady-state re-parse of the same shape this reuses nested structs, arrays, and strings in place — effectively zero allocation after the first call.

Next steps ​