Detalhes do pacote

json-as

JairusSW8.3kMIT1.1.17

The only JSON library you'll need for AssemblyScript. SIMD enabled

assemblyscript, json, serialize, deserialize

readme (leia-me)

     ██ ███████  ██████  ███    ██        █████  ███████
     ██ ██      ██    ██ ████   ██       ██   ██ ██
██ ███████ ██ ██ ██ ██ ██ █████ ███████ ███████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ █████ ███████ ██████ ██ ████ ██ ██ ███████
AssemblyScript - v1.1.17

📝 About

JSON is the de-facto serialization format of modern web applications, but its serialization and deserialization remain a significant performance bottleneck, especially at scale. Traditional parsing approaches are computationally expensive, adding unnecessary overhead to both clients and servers. This library is designed to mitigate this by leveraging SIMD acceleration and highly optimized transformations.

📚 Contents

💾 Installation

npm install json-as

Add the --transform to your asc command (e.g. in package.json)

--transform json-as/transform

Alternatively, add it to your asconfig.json

{
  "options": {
    "transform": ["json-as/transform"]
  }
}

If you'd like to see the code that the transform generates, run the build step with DEBUG=true

🪄 Usage

import { JSON } from "json-as";

@json
class Vec3 {
  x: f32 = 0.0;
  y: f32 = 0.0;
  z: f32 = 0.0;
}

@json
class Player {
  @alias("first name")
  firstName!: string;
  lastName!: string;
  lastActive!: i32[];
  // Drop in a code block, function, or expression that evaluates to a boolean
  @omitif((self: Player) => self.age < 18)
  age!: i32;
  @omitnull()
  pos!: Vec3 | null;
  isVerified!: boolean;
}

const player: Player = {
  firstName: "Jairus",
  lastName: "Tanaka",
  lastActive: [3, 9, 2025],
  age: 18,
  pos: {
    x: 3.4,
    y: 1.2,
    z: 8.3,
  },
  isVerified: true,
};

const serialized = JSON.stringify<Player>(player);
const deserialized = JSON.parse<Player>(serialized);

console.log("Serialized    " + serialized);
console.log("Deserialized  " + JSON.stringify(deserialized));

🔍 Examples

🏷️ Omitting Fields

This library allows selective omission of fields during serialization using the following decorators:

@omit

This decorator excludes a field from serialization entirely.

@json
class Example {
  name!: string;
  @omit
  SSN!: string;
}

const obj = new Example();
obj.name = "Jairus";
obj.SSN = "123-45-6789";

console.log(JSON.stringify(obj)); // { "name": "Jairus" }

@omitnull

This decorator omits a field only if its value is null.

@json
class Example {
  name!: string;
  @omitnull()
  optionalField!: string | null;
}

const obj = new Example();
obj.name = "Jairus";
obj.optionalField = null;

console.log(JSON.stringify(obj)); // { "name": "Jairus" }

@omitif((self: this) => condition)

This decorator omits a field based on a custom predicate function.

@json
class Example {
  name!: string;
  @omitif((self: Example) => self.age <= 18)
  age!: number;
}

const obj = new Example();
obj.name = "Jairus";
obj.age = 18;

console.log(JSON.stringify(obj)); // { "name": "Jairus" }

obj.age = 99;

console.log(JSON.stringify(obj)); // { "name": "Jairus", "age": 99 }

If age were higher than 18, it would be included in the serialization.

🗳️ Using nullable primitives

AssemblyScript doesn't support using nullable primitive types, so instead, json-as offers the JSON.Box class to remedy it.

For example, this schema won't compile in AssemblyScript:

@json
class Person {
  name!: string;
  age: i32 | null = null;
}

Instead, use JSON.Box to allow nullable primitives:

@json
class Person {
  name: string;
  age: JSON.Box<i32> | null = null;
  constructor(name: string) {
    this.name = name;
  }
}

const person = new Person("Jairus");
console.log(JSON.stringify(person)); // {"name":"Jairus","age":null}

person.age = new JSON.Box<i32>(18); // Set age to 18
console.log(JSON.stringify(person)); // {"name":"Jairus","age":18}

📤 Working with unknown or dynamic data

Sometimes it's necessary to work with unknown data or data with dynamic types.

Because AssemblyScript is a statically-typed language, that typically isn't allowed, so json-as provides the JSON.Value and JSON.Obj types.

Here's a few examples:

Working with multi-type arrays

When dealing with arrays that have multiple types within them, eg. ["string",true,["array"]], use JSON.Value[]

const a = JSON.parse<JSON.Value[]>('["string",true,["array"]]');
console.log(JSON.stringify(a[0])); // "string"
console.log(JSON.stringify(a[1])); // true
console.log(JSON.stringify(a[2])); // ["array"]

Working with unknown objects

When dealing with an object with an unknown structure, use the JSON.Obj type

const obj = JSON.parse<JSON.Obj>('{"a":3.14,"b":true,"c":[1,2,3],"d":{"x":1,"y":2,"z":3}}');

console.log("Keys: " + obj.keys().join(" ")); // a b c d
console.log(
  "Values: " +
    obj
      .values()
      .map<string>((v) => JSON.stringify(v))
      .join(" "),
); // 3.14 true [1,2,3] {"x":1,"y":2,"z":3}

const y = obj.get("d")!.get<JSON.Obj>().get("y")!;
console.log('o1["d"]["y"] = ' + y.toString()); // o1["d"]["y"] = 2

Working with dynamic types within a schema

More often, objects will be completely statically typed except for one or two values.

In such cases, JSON.Value can be used to handle fields that may hold different types at runtime.

@json
class DynamicObj {
  id: i32 = 0;
  name: string = "";
  data!: JSON.Value; // Can hold any type of value
}

const obj = new DynamicObj();
obj.id = 1;
obj.name = "Example";
obj.data = JSON.parse<JSON.Value>('{"key":"value"}'); // Assigning an object

console.log(JSON.stringify(obj)); // {"id":1,"name":"Example","data":{"key":"value"}}

obj.data = JSON.Value.from<i32>(42); // Changing to an integer
console.log(JSON.stringify(obj)); // {"id":1,"name":"Example","data":42}

obj.data = JSON.Value.from("a string"); // Changing to a string
console.log(JSON.stringify(obj)); // {"id":1,"name":"Example","data":"a string"}

🏗️ Using Raw JSON strings

Sometimes its necessary to simply copy a string instead of serializing it.

For example, the following data would typically be serialized as:

const map = new Map<string, string>();
map.set("pos", '{"x":1.0,"y":2.0,"z":3.0}');

console.log(JSON.stringify(map));
// {"pos":"{\"x\":1.0,\"y\":2.0,\"z\":3.0}"}
// pos's value (Vec3) is contained within a string... ideally, it should be left alone

If, instead, one wanted to insert Raw JSON into an existing schema/data structure, they could make use of the JSON.Raw type to do so:

const map = new Map<string, JSON.Raw>();
map.set("pos", new JSON.Raw('{"x":1.0,"y":2.0,"z":3.0}'));

console.log(JSON.stringify(map));
// {"pos":{"x":1.0,"y":2.0,"z":3.0}}
// Now its properly formatted JSON where pos's value is of type Vec3 not string!

📝 Working with enums

By default, enums arn't supported by json-as. However, you can use a workaround:

namespace Foo {
  export const bar = "a";
  export const baz = "b";
  export const gob = "c";
}

type Foo = string;

const serialized = JSON.stringify<Foo>(Foo.bar);
// "a"

⚒️ Using custom serializers or deserializers

This library supports custom serialization and deserialization methods, which can be defined using the @serializer and @deserializer decorators.

Here's an example of creating a custom data type called Point which serializes to (x,y)

import { bytes } from "json-as/assembly/util";

@json
class Point {
  x: f64 = 0.0;
  y: f64 = 0.0;
  constructor(x: f64, y: f64) {
    this.x = x;
    this.y = y;
  }

  @serializer
  serializer(self: Point): string {
    return `(${self.x},${self.y})`;
  }

  @deserializer
  deserializer(data: string): Point {
    const dataSize = bytes(data);
    if (dataSize <= 2) throw new Error("Could not deserialize provided data as type Point");

    const c = data.indexOf(",");
    const x = data.slice(1, c);
    const y = data.slice(c + 1, data.length - 1);

    return new Point(f64.parse(x), f64.parse(y));
  }
}

const obj = new Point(3.5, -9.2);

const serialized = JSON.stringify<Point>(obj);
const deserialized = JSON.parse<Point>(serialized);

console.log("Serialized    " + serialized);
console.log("Deserialized  " + JSON.stringify(deserialized));

The serializer function converts a Point instance into a string format (x,y).

The deserializer function parses the string (x,y) back into a Point instance.

These functions are then wrapped before being consumed by the json-as library:

@inline __SERIALIZE_CUSTOM(): void {
  const data = this.serializer(this);
  const dataSize = data.length << 1;
  memory.copy(bs.offset, changetype<usize>(data), dataSize);
  bs.offset += dataSize;
}

@inline __DESERIALIZE_CUSTOM(data: string): Point {
  return this.deserializer(data);
}

This allows custom serialization while maintaining a generic interface for the library to access.

⚡ Performance

The json-as library has been optimized to achieve near-gigabyte-per-second JSON processing speeds through SIMD acceleration and highly efficient transformations. Below are detailed statistics comparing performance metrics such as build time, operations-per-second, and throughput.

🔍 Comparison to JavaScript

These benchmarks compare this library to JavaScript's native JSON.stringify and JSON.parse functions.

Table 1 - AssemblyScript (LLVM)

Test Case Size Serialization (ops/s) Deserialization (ops/s) Serialization (MB/s) Deserialization (MB/s)
Vector3 Object 38 bytes 26,611,226 ops/s 32,160,804 ops/s 1,357 MB/s 1,348 MB/s
Alphabet String 104 bytes 13,617,021 ops/s 18,390,804 ops/s 1,416 MB/s 1,986 MB/s
Small Object 88 bytes 24,242,424 ops/s 12,307,692 ops/s 2,133 MB/s 1,083 MB/s
Medium Object 494 bytes 4,060,913 ops/s 1,396,160 ops/s 2,006 MB/s 689.7 MB/s
Large Object 3374 bytes 479,616 ops/s 132,802 ops/s 2,074 MB/s 448.0 MB/s

Table 2 - JavaScript (V8)

Test Case Size Serialization (ops/s) Deserialization (ops/s) Serialization (MB/s) Deserialization (MB/s)
Vector3 Object 38 bytes 8,791,209 ops/s 5,369,12 ops/s 357.4 MB/s 204.3 MB/s
Alphabet String 104 bytes 13,793,103 ops/s 14,746,544 ops/s 1,416 MB/s 1,592 MB/s
Small Object 88 bytes 8,376,963 ops/s 4,968,944 ops/s 737.1 MB/s 437.2 MB/s
Medium Object 494 bytes 2,395,210 ops/s 1,381,693 ops/s 1,183 MB/s 682.5 MB/s
Large Object 3374 bytes 222,222 ops/s 117,233 ops/s 749.7 MB/s 395.5 MB/s

📌 Insights

  • JSON-AS consistently outperforms JavaScript's native implementation.

  • Serialization Speed:

    • JSON-AS achieves speeds up to 2,133 MB/s, significantly faster than JavaScript's peak of 1,416 MB/s.
    • Large objects see the biggest improvement, with JSON-AS at 2,074 MB/s vs. JavaScript’s 749.7 MB/s.
  • Deserialization Speed:

    • JSON-AS reaches 1,986 MB/s, while JavaScript caps at 1,592 MB/s.
    • Small and medium objects see the most significant performance boost overall.

📈 Comparison to v0.9.x version

Table 1 - v1.0.0

Test Case Size Serialization (ops/s) Deserialization (ops/s) Serialization (MB/s) Deserialization (MB/s)
Vector3 Object 38 bytes 35,714,285 ops/s 35,435,552 ops/s 1,357 MB/s 1,348 MB/s
Alphabet String 104 bytes 13,617,021 ops/s 18,390,804 ops/s 1,416 MB/s 1,986 MB/s
Small Object 88 bytes 24,242,424 ops/s 12,307,692 ops/s 2,133 MB/s 1,083 MB/s
Medium Object 494 bytes 4,060,913 ops/s 1,396,160 ops/s 2,006 MB/s 689.7 MB/s
Large Object 3374 bytes 614,754 ops/s 132,802 ops/s 2,074 MB/s 448.0 MB/s

Table 2 - v0.9.29

Test Case Size Serialization (ops/s) Deserialization (ops/s) Serialization (MB/s) Deserialization (MB/s)
Vector3 Object 38 bytes 6,896,551 ops/s 10,958,904 ops/s 262.1 MB/s 416.4 MB/s
Alphabet String 104 bytes 5,128,205 ops/s 8,695,652 ops/s 533.3 MB/s 939.1 MB/s
Small Object 88 bytes 4,953,560 ops/s 3,678,160 ops/s 435.9 MB/s 323.7 MB/s
Medium Object 494 bytes 522,193 ops/s 508,582 ops/s 258.0 MB/s 251.2 MB/s
Large Object 3374 bytes 51,229 ops/s 65,585 ops/s 172.8 MB/s 221.3 MB/s

📌 Insights:

  • Massive performance improvements in JSON-AS v1.0.0:
  • Serialization is 2-12x faster (e.g., Large Object: 2,074 MB/s vs. 172.8 MB/s).
  • Deserialization is 2-3x faster (e.g., Large Object: 1,348 MB/s vs. 221.3 MB/s).
  • Vector3 Object serialization improved from 416 MB/s to 1,357 MB/s--a 3x benefit through new code generation techniques.

🔭 What's Next

  • Theorize plans to keep key-order in generated schemas
  • Generate optimized deserialization methods
  • Inline specific hot code paths
  • Implement error handling implementation

🐛 Debugging

JSON_DEBUG=1 - Prints out generated code at compile-time JSON_DEBUG=2 - The above and prints keys/values as they are deserialized JSON_WRITE=path-to-file.ts - Writes out generated code to path-to-file.json.ts for easy inspection

📃 License

This project is distributed under an open source license. You can view the full license using the following link: License

📫 Contact

Please send all issues to GitHub Issues and to converse, please send me an email at me@jairus.dev

changelog (log de mudanças)

Change Log

2025-06-17 - 1.1.17

  • fix: add support for classes within namespaces #147

2025-06-12 - 1.1.16

  • tests: properly support nulls (in testing lib)
  • fix: initialize generic properties correctly
  • fix: make generated imports compatible with windows
  • feat: add support for fields marked with readonly

2025-06-09 - 1.1.15

  • feat: add .as<T>() method to JSON.Value
  • chore: remove all references to __SERIALIZE_CUSTOM
  • feat: add support for StaticArray serialization
  • feat: support JSON.Raw in array types
  • tests: add tests for JSON.Raw[]

2025-05-29 - 1.1.14

  • fix: hotfix schema resolver

2025-05-29 - 1.1.13

  • fix: small issues with schema linking
  • tests: add tests for schema linking and discovery

2025-05-29 - 1.1.12

  • fix: add helpful warning on unknown or unaccessible types in fields
  • feat: support deserialization of class generics
  • fix: add support for numerical generics
  • tests: add proper testing for generics
  • feat: support type aliases with a custom type resolver/linker
  • chore: add other linkers to tsconfig and clean up
  • feat: add type alias resolving

2025-05-28 - 1.1.11

  • fix: class resolving should only search top level statements for class declarations
  • fix: add helpful error if class is missing an @json decorator
  • fix: properly calculate relative path when json-as is a library
  • fix: add proper null check when resolving imported classes

2025-05-28 - 1.1.10

  • feat: add more debug levels (1 = print transform code, 2 = print keys/values at runtime)
  • feat: add write out feature (JSON_WRITE=path-to-file.ts) which writes out generated code
  • fix: complete full parity between port and original version for correct deserialization of all types
  • feat: add proper schema resolution and dependency resolution
  • feat: add proper type resolution to schema fields
  • fix: properly calculate the relative path between imports to modules

2025-05-27 - 1.1.9

  • change: strict mode is disabled by default. Enable it with JSON_STRICT=true
  • fix: should ignore properties of same length and type if no matching key exists
  • fix: should ignore properties of different type if no matching key exists
  • fix: should ignore complex properties if no matching key exists

2025-05-27 - 1.1.8

  • feat: add support for calling JSON.stringify/JSON.parse methods inside of custom serializers, but not yet deserializers

2025-05-27 - 1.1.7

  • fix: bad boolean logic to decide whether to add 2nd break statement

2025-05-23 - 1.1.6

  • fix: null and boolean fields would miscalculate offsets when deserializing

2025-05-23 - 1.1.5

  • fix: index.js didn't point to correct file, thus creating a compiler crash

2025-05-23 - 1.1.4

  • revert: grouping properties in favor of memory.compare

2025-05-23 - 1.1.3

  • feat: group properties of structs before code generation
  • fix: break out of switch case after completion
  • ci: make compatible with act for local testing

2025-05-22 - 1.1.2

  • fix: correct small typos in string value deserialization port

2025-05-22 - 1.1.1

  • fix: remove random logs

2025-05-22 - 1.1.0

  • fix: change _DESERIALIZE<T> to _JSON_T to avoid populating local scope

2025-05-22 - 1.0.9

  • fix: #132
  • feat: allow base classes to use their child classes if the signatures match
  • perf: rewrite struct deserialization to be significantly faster
  • fix: #131 Generic classes with custom deserializer crashing
  • fix: #66 Throw error when additional keys are in JSON

2025-05-21 - 1.0.8

  • fix: inline warnings on layer-2 serialize and deserialize functions
  • feat: fully support JSON.Obj and JSON.Box everywhere
  • fix: temp disable SIMD
  • feat: write fair benchmarks with v8 using jsvu

2025-05-14 - 1.0.7

2025-05-12 - 1.0.6

  • fix: support zero-param serialization and make sure types are consistent
  • fix: #124

2025-05-11 - 1.0.5

  • feat: add sanity checks for badly formatted strings
  • fix: #120 handle empty JSON.Obj serialization
  • feat: add SIMD optimization if SIMD is enabled by user
  • fix: handle structs with nullable array as property #123
  • fix: struct serialization from writing to incorrect parts of memory when parsing nested structs #125
  • chore: add two new contributors

2025-04-07 - 1.0.4

  • fix: paths must be resolved as POSIX in order to be valid TypeScript imports #116

2025-03-24 - 1.0.3

  • fix: make transform windows-compatible #119

2025-03-19 - 1.0.2

  • fix: include check for nullable types for properties when deserialization is called internally #118

2025-03-10 - 1.0.1

  • docs: add comprehensive performance metrics

2025-03-09 - 1.0.0

  • fix: relative paths pointing through node_modules would create a second Source
  • feat: move behavior of --lib into transform itself
  • fix: object with an object as a value containing a rhs bracket or brace would exit early 3b33e94

2025-03-04 - 1.0.0-beta.17

  • fix: forgot to build transform

2025-03-04 - 1.0.0-beta.16

  • fix: isPrimitive should only trigger on actual primitives

2025-03-04 - 1.0.0-beta.15

  • fix: deserialize custom should take in string

2025-03-04 - 1.0.0-beta.14

  • fix: reference to nonexistent variable during custom deserialization layer 2

2025-03-04 - 1.0.0-beta.13

  • fix: forgot to actually build the transform

2025-03-04 - 1.0.0-beta.12

  • fix: build transform

2025-03-04 - 1.0.0-beta.11

  • fix: wrongly assumed pointer types within arbitrary deserialization
  • fix: wrong pointer type being passed during map deserialization

2025-03-04 - 1.0.0-beta.10

  • fix: transform not generating the right load operations for keys
  • fix: whitespace not working in objects or struct deserialization
  • fix: JSON.Raw not working when deserializing as Map<string, JSON.Raw>

2025-03-03 - 1.0.0-beta.9

  • rename: change libs folder to lib

2025-03-03 - 1.0.0-beta.8

  • docs: add instructions for using --lib in README

2025-03-03 - 1.0.0-beta.7

  • fix: add as-bs to --lib section
  • chore: clean up transform
  • refactor: transform should import ~lib/as-bs.ts instead of relative path

2025-03-01 - 1.0.0-beta.6

  • fix: import from base directory index.ts

2025-03-01 - 1.0.0-beta.5

  • fix: revert pull request #112

2025-02-25 - 1.0.0-beta.4

  • fix: warn on presence of invalid types contained in a schema #112

2025-02-25 - 1.0.0-beta.3

  • feat: change JSON.Raw to actual class to facilitate proper support without transformations
  • fix: remove old JSON.Raw logic from transform code

2025-02-25 - 1.0.0-beta.2

  • feat: add support for custom serializers and deserializers #110

2025-02-22 - 1.0.0-beta.1

  • perf: add benchmarks for both AssemblyScript and JavaScript
  • docs: publish preliminary benchmark results
  • tests: ensure nested serialization works and add to tests
  • feat: finish arbitrary type implementation
  • feat: introduce JSON.Obj to handle objects effectively
  • feat: reimplement arbitrary array deserialization
  • fix: remove brace check on array deserialization
  • feat: introduce native support for JSON.Obj transformations
  • feat: implement arbitrary object serialization
  • fix: deserialization of booleans panics on false
  • fix: bs.resize should be type-safe
  • impl: add JSON.Obj type as prototype to handle arbitrary object structures
  • chore: rename static objects (schemas) to structs and name arbitrary objects as obj
  • tests: add proper tests for arbitrary types
  • fix: empty method generation using outdated function signature
  • docs: update readme to be more concise

2025-02-13 - 1.0.0-alpha.4

  • feat: reintroduce support for Box<T>-wrapped primitive types
  • tests: add extensive tests to all supported types
  • fix: 6-byte keys being recognized on deserialize
  • perf: take advantage of aligned memory to use a single 64-bit load on 6-byte keys
  • fix: bs.proposeSize() should increment stackSize by size instead of setting it
  • fix: allow runtime to manage bs.buffer
  • fix: memory leaks in bs module
  • fix: add (possibly temporary) JSON.Memory.shrink() to shrink memory in bs
  • perf: prefer growing memory by nextPowerOf2(size + 64) for less reallocations
  • tests: add boolean tests to Box<T>
  • fix: serialization of non-growable data types should grow bs.stackSize

2025-01-31 - 1.0.0-alpha.3

  • fix: write to proper offset when deserializing string with \u0000-type escapes
  • fix: simplify and fix memory offset issues with bs module
  • fix: properly predict minimum size of to-be-serialized schemas
  • fix: replace as-test with temporary framework to mitigate json-as versioning issues
  • fix: fix multiple memory leaks during serialization
  • feat: align memory allocations for better performance
  • feat: achieve a space complexity of O(n) for serialization operations, unless dealing with \u0000-type escapes

2025-01-20 - 1.0.0-alpha.2

  • fix: disable SIMD in generated transform code by default
  • fix: re-add as-bs dependency so that it will not break in non-local environments
  • fix: remove AS201 'conversion from type usize to i32' warning
  • fix: add as-bs to peer dependencies so only one version is installed
  • fix: point as-bs imports to submodule
  • fix: remove submodule in favor of static module
  • fix: bs.ensureSize would not grow and thus cause memory faults
  • fix: bs.ensureSize triggering unintentionally

2025-01-20 - 1.0.0-alpha.1

  • feat: finish implementation of arbitrary data serialization and deserialization using JSON.Value
  • feat: reinstate usage of JSON.Box<T>() to support nullable primitive types
  • feat: eliminate the need to import the JSON namespace when defining a schema
  • feat: reduce memory usage so that it is viable for low-memory environments
  • feat: write to a central buffer and reduce memory overhead
  • feat: rewrite the transform to properly resolve schemas and link them together
  • feat: pre-allocate and compute the minimum size of a schema to avoid memory out of range errors