Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: near/borsh-js
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.0.0
Choose a base ref
...
head repository: near/borsh-js
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v2.0.0
Choose a head ref
  • 7 commits
  • 11 files changed
  • 3 contributors

Commits on Nov 21, 2023

  1. Copy the full SHA
    b6a6838 View commit details
  2. Fixed explanation of enums in README.md (#68)

    Co-authored-by: Bo Yao <bo@near.org>
    gagdiez and ailisp authored Nov 21, 2023
    Copy the full SHA
    ccf0b00 View commit details
  3. Update README.md

    Updated API with correct `serialize` / `deserialize` parameters
    gagdiez authored Nov 21, 2023
    Copy the full SHA
    dc9ad87 View commit details

Commits on Nov 23, 2023

  1. fix: ut8-string encode/decode (#76)

    * fix: ut8-string encode/decode
    
    There was a bug by which we were storing the strings as unicode
    bytes instead of utf8 bytes. This was a bug since the specification
    clearly says that the encoding must be utf8.
    
    This commit fixes such bug using the TextEncode / TextDecode tools,
    which are widely supported by modern browsers and node versions.
    
    * fix: utf8 without TextEncoder/Decoder
    
    * more tests
    gagdiez authored Nov 23, 2023
    Copy the full SHA
    abe2701 View commit details
  2. feat: remove unnecesary utils

    gagdiez committed Nov 23, 2023
    Copy the full SHA
    4a5482d View commit details
  3. Merge pull request #77 from near/fix-utils

    feat: remove unnecesary utils
    gagdiez authored Nov 23, 2023
    Copy the full SHA
    50f5a18 View commit details

Commits on Nov 24, 2023

  1. release 2.0 (#78)

    ailisp authored Nov 24, 2023
    Copy the full SHA
    3783185 View commit details
Showing with 137 additions and 23 deletions.
  1. +4 −4 README.md
  2. +20 −1 borsh-ts/deserialize.ts
  3. +24 −5 borsh-ts/serialize.ts
  4. +4 −0 borsh-ts/test/(de)serialize.test.js
  5. +1 −1 examples/cjs/package.json
  6. +1 −1 examples/esm/package.json
  7. +21 −1 lib/cjs/deserialize.js
  8. +20 −4 lib/cjs/serialize.js
  9. +21 −1 lib/esm/deserialize.js
  10. +20 −4 lib/esm/serialize.js
  11. +1 −1 package.json
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -39,8 +39,8 @@ const decoded = borsh.deserialize(schema, encoded);

## API
The package exposes the following functions:
- `serialize(schema: Schema, obj: any): Uint8Array` - serializes an object `obj` according to the schema `schema`.
- `deserialize(schema: Schema, buffer: Uint8Array, class?: Class): any` - deserializes an object according to the schema `schema` from the buffer `buffer`. If the optional parameter `class` is present, the deserialized object will be an of `class`.
- `serialize(schema: Schema, obj: any, validate: boolean = true): Uint8Array` - serializes an object `obj` according to the schema `schema`. Setting `validate` to false will skip the validation of the `schema`.
- `deserialize(schema: Schema, buffer: Uint8Array, validate: boolean = true): any` - deserializes an object according to the schema `schema` from the buffer `buffer`. Setting `validate` to false will skip the validation of the `schema`.

## Schemas
Schemas are used to describe the structure of the data being serialized or deserialized. They are used to
@@ -62,7 +62,7 @@ More complex objects are described by a JSON object. The following types are sup
- `{ option: Schema }` - an optional object. The type of the object is described by the `type` field.
- `{ map: { key: Schema, value: Schema }}` - a map. The type of the keys and values are described by the `key` and `value` fields respectively.
- `{ set: Schema }` - a set. The type of the elements is described by the `type` field.
- `{ enum: [{ className1: { struct: {...} } }, { className2: { struct: {...} } }, ... ] }` - an enum. The variants of the enum are described by the `className1`, `className2`, etc. fields. The variants are structs.
- `{ enum: [ { struct: { className1: structSchema1 } }, { struct: { className2: structSchema2 } }, ... ] }` - an enum. The variants of the enum are described by the `className1`, `className2`, etc. fields. The variants are structs.
- `{ struct: { field1: Schema1, field2: Schema2, ... } }` - a struct. The fields of the struct are described by the `field1`, `field2`, etc. fields.

### Type Mappings
@@ -119,4 +119,4 @@ When publishing to npm use [np](https://github.com/sindresorhus/np).
This repository is distributed under the terms of both the MIT license and the Apache License (Version 2.0).
See [LICENSE-MIT](LICENSE-MIT.txt) and [LICENSE-APACHE](LICENSE-APACHE) for details.

[Borsh]: https://borsh.io
[Borsh]: https://borsh.io
21 changes: 20 additions & 1 deletion borsh-ts/deserialize.ts
Original file line number Diff line number Diff line change
@@ -54,7 +54,26 @@ export class BorshDeserializer {
decode_string(): string {
const len: number = this.decode_integer('u32') as number;
const buffer = new Uint8Array(this.buffer.consume_bytes(len));
return String.fromCharCode.apply(null, buffer);

// decode utf-8 string without using TextDecoder
// first get all bytes to single byte code points
const codePoints = [];
for (let i = 0; i < len; ++i) {
const byte = buffer[i];
if (byte < 0x80) {
codePoints.push(byte);
} else if (byte < 0xE0) {
codePoints.push(((byte & 0x1F) << 6) | (buffer[++i] & 0x3F));
} else if (byte < 0xF0) {
codePoints.push(((byte & 0x0F) << 12) | ((buffer[++i] & 0x3F) << 6) | (buffer[++i] & 0x3F));
} else {
const codePoint = ((byte & 0x07) << 18) | ((buffer[++i] & 0x3F) << 12) | ((buffer[++i] & 0x3F) << 6) | (buffer[++i] & 0x3F);
codePoints.push(codePoint);
}
}

// then decode code points to utf-8
return String.fromCodePoint(...codePoints);
}

decode_boolean(): boolean {
29 changes: 24 additions & 5 deletions borsh-ts/serialize.ts
Original file line number Diff line number Diff line change
@@ -63,13 +63,32 @@ export class BorshSerializer {
this.checkTypes && utils.expect_type(value, 'string', this.fieldPath);
const _value = value as string;

// 4 bytes for length
this.encoded.store_value(_value.length, 'u32');

// string bytes
// encode to utf8 bytes without using TextEncoder
const utf8Bytes: number[] = [];
for (let i = 0; i < _value.length; i++) {
this.encoded.store_value(_value.charCodeAt(i), 'u8');
let charCode = _value.charCodeAt(i);

if (charCode < 0x80) {
utf8Bytes.push(charCode);
} else if (charCode < 0x800) {
utf8Bytes.push(0xc0 | (charCode >> 6), 0x80 | (charCode & 0x3f));
} else if (charCode < 0xd800 || charCode >= 0xe000) {
utf8Bytes.push(0xe0 | (charCode >> 12), 0x80 | ((charCode >> 6) & 0x3f), 0x80 | (charCode & 0x3f));
} else {
i++;
charCode = 0x10000 + (((charCode & 0x3ff) << 10) | (_value.charCodeAt(i) & 0x3ff));
utf8Bytes.push(
0xf0 | (charCode >> 18),
0x80 | ((charCode >> 12) & 0x3f),
0x80 | ((charCode >> 6) & 0x3f),
0x80 | (charCode & 0x3f),
);
}
}

// 4 bytes for length + string bytes
this.encoded.store_value(utf8Bytes.length, 'u32');
this.encoded.store_bytes(new Uint8Array(utf8Bytes));
}

encode_boolean(value: unknown): void {
4 changes: 4 additions & 0 deletions borsh-ts/test/(de)serialize.test.js
Original file line number Diff line number Diff line change
@@ -40,6 +40,10 @@ test('serialize booleans', async () => {

test('serialize strings', async () => {
check_roundtrip('h"i', 'string', [3, 0, 0, 0, 104, 34, 105]);
check_roundtrip('Chévere', 'string', [8, 0, 0, 0, 67, 104, 195, 169, 118, 101, 114, 101]);
check_roundtrip('!ǬЇЉي࠺👍ઠ൧࿄ሒᘻᏠᬅᡝ࠻', 'string', [43, 0, 0, 0, 33, 199, 172, 208, 135, 208, 137, 217, 138, 224, 160, 186, 240, 159, 145, 141, 224, 170, 160, 224, 181, 167, 224, 191, 132, 225, 136, 146, 225, 152, 187, 225, 143, 160, 225, 172, 133, 225, 161, 157, 224, 160, 187]);
check_roundtrip('óñ@‡؏ث 漢࠶⭐🔒􀀀', 'string', [30, 0, 0, 0, 195, 179, 195, 177, 64, 226, 128, 161, 216, 143, 216, 171, 32, 230, 188, 162, 224, 160, 182, 226, 173, 144, 240, 159, 148, 146, 244, 128, 128, 128]);
check_roundtrip('f © bar 𝌆 baz ☃ qux', 'string', [25, 0, 0, 0, 102, 32, 194, 169, 32, 98, 97, 114, 32, 240, 157, 140, 134, 32, 98, 97, 122, 32, 226, 152, 131, 32, 113, 117, 120]);
});

test('serialize floats', async () => {
2 changes: 1 addition & 1 deletion examples/cjs/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "cjs-example",
"private": true,
"version": "1.0.0",
"version": "2.0.0",
"description": "",
"main": "index.js",
"dependencies": {
2 changes: 1 addition & 1 deletion examples/esm/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "esm-example",
"private": true,
"version": "1.0.0",
"version": "2.0.0",
"description": "",
"type": "module",
"main": "index.js",
22 changes: 21 additions & 1 deletion lib/cjs/deserialize.js
Original file line number Diff line number Diff line change
@@ -55,7 +55,27 @@ var BorshDeserializer = /** @class */ (function () {
BorshDeserializer.prototype.decode_string = function () {
var len = this.decode_integer('u32');
var buffer = new Uint8Array(this.buffer.consume_bytes(len));
return String.fromCharCode.apply(null, buffer);
// decode utf-8 string without using TextDecoder
// first get all bytes to single byte code points
var codePoints = [];
for (var i = 0; i < len; ++i) {
var byte = buffer[i];
if (byte < 0x80) {
codePoints.push(byte);
}
else if (byte < 0xE0) {
codePoints.push(((byte & 0x1F) << 6) | (buffer[++i] & 0x3F));
}
else if (byte < 0xF0) {
codePoints.push(((byte & 0x0F) << 12) | ((buffer[++i] & 0x3F) << 6) | (buffer[++i] & 0x3F));
}
else {
var codePoint = ((byte & 0x07) << 18) | ((buffer[++i] & 0x3F) << 12) | ((buffer[++i] & 0x3F) << 6) | (buffer[++i] & 0x3F);
codePoints.push(codePoint);
}
}
// then decode code points to utf-8
return String.fromCodePoint.apply(String, codePoints);
};
BorshDeserializer.prototype.decode_boolean = function () {
return this.buffer.consume_value('u8') > 0;
24 changes: 20 additions & 4 deletions lib/cjs/serialize.js
Original file line number Diff line number Diff line change
@@ -84,12 +84,28 @@ var BorshSerializer = /** @class */ (function () {
BorshSerializer.prototype.encode_string = function (value) {
this.checkTypes && utils.expect_type(value, 'string', this.fieldPath);
var _value = value;
// 4 bytes for length
this.encoded.store_value(_value.length, 'u32');
// string bytes
// encode to utf8 bytes without using TextEncoder
var utf8Bytes = [];
for (var i = 0; i < _value.length; i++) {
this.encoded.store_value(_value.charCodeAt(i), 'u8');
var charCode = _value.charCodeAt(i);
if (charCode < 0x80) {
utf8Bytes.push(charCode);
}
else if (charCode < 0x800) {
utf8Bytes.push(0xc0 | (charCode >> 6), 0x80 | (charCode & 0x3f));
}
else if (charCode < 0xd800 || charCode >= 0xe000) {
utf8Bytes.push(0xe0 | (charCode >> 12), 0x80 | ((charCode >> 6) & 0x3f), 0x80 | (charCode & 0x3f));
}
else {
i++;
charCode = 0x10000 + (((charCode & 0x3ff) << 10) | (_value.charCodeAt(i) & 0x3ff));
utf8Bytes.push(0xf0 | (charCode >> 18), 0x80 | ((charCode >> 12) & 0x3f), 0x80 | ((charCode >> 6) & 0x3f), 0x80 | (charCode & 0x3f));
}
}
// 4 bytes for length + string bytes
this.encoded.store_value(utf8Bytes.length, 'u32');
this.encoded.store_bytes(new Uint8Array(utf8Bytes));
};
BorshSerializer.prototype.encode_boolean = function (value) {
this.checkTypes && utils.expect_type(value, 'boolean', this.fieldPath);
22 changes: 21 additions & 1 deletion lib/esm/deserialize.js
Original file line number Diff line number Diff line change
@@ -52,7 +52,27 @@ var BorshDeserializer = /** @class */ (function () {
BorshDeserializer.prototype.decode_string = function () {
var len = this.decode_integer('u32');
var buffer = new Uint8Array(this.buffer.consume_bytes(len));
return String.fromCharCode.apply(null, buffer);
// decode utf-8 string without using TextDecoder
// first get all bytes to single byte code points
var codePoints = [];
for (var i = 0; i < len; ++i) {
var byte = buffer[i];
if (byte < 0x80) {
codePoints.push(byte);
}
else if (byte < 0xE0) {
codePoints.push(((byte & 0x1F) << 6) | (buffer[++i] & 0x3F));
}
else if (byte < 0xF0) {
codePoints.push(((byte & 0x0F) << 12) | ((buffer[++i] & 0x3F) << 6) | (buffer[++i] & 0x3F));
}
else {
var codePoint = ((byte & 0x07) << 18) | ((buffer[++i] & 0x3F) << 12) | ((buffer[++i] & 0x3F) << 6) | (buffer[++i] & 0x3F);
codePoints.push(codePoint);
}
}
// then decode code points to utf-8
return String.fromCodePoint.apply(String, codePoints);
};
BorshDeserializer.prototype.decode_boolean = function () {
return this.buffer.consume_value('u8') > 0;
24 changes: 20 additions & 4 deletions lib/esm/serialize.js
Original file line number Diff line number Diff line change
@@ -58,12 +58,28 @@ var BorshSerializer = /** @class */ (function () {
BorshSerializer.prototype.encode_string = function (value) {
this.checkTypes && utils.expect_type(value, 'string', this.fieldPath);
var _value = value;
// 4 bytes for length
this.encoded.store_value(_value.length, 'u32');
// string bytes
// encode to utf8 bytes without using TextEncoder
var utf8Bytes = [];
for (var i = 0; i < _value.length; i++) {
this.encoded.store_value(_value.charCodeAt(i), 'u8');
var charCode = _value.charCodeAt(i);
if (charCode < 0x80) {
utf8Bytes.push(charCode);
}
else if (charCode < 0x800) {
utf8Bytes.push(0xc0 | (charCode >> 6), 0x80 | (charCode & 0x3f));
}
else if (charCode < 0xd800 || charCode >= 0xe000) {
utf8Bytes.push(0xe0 | (charCode >> 12), 0x80 | ((charCode >> 6) & 0x3f), 0x80 | (charCode & 0x3f));
}
else {
i++;
charCode = 0x10000 + (((charCode & 0x3ff) << 10) | (_value.charCodeAt(i) & 0x3ff));
utf8Bytes.push(0xf0 | (charCode >> 18), 0x80 | ((charCode >> 12) & 0x3f), 0x80 | ((charCode >> 6) & 0x3f), 0x80 | (charCode & 0x3f));
}
}
// 4 bytes for length + string bytes
this.encoded.store_value(utf8Bytes.length, 'u32');
this.encoded.store_bytes(new Uint8Array(utf8Bytes));
};
BorshSerializer.prototype.encode_boolean = function (value) {
this.checkTypes && utils.expect_type(value, 'boolean', this.fieldPath);
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "borsh",
"version": "1.0.0",
"version": "2.0.0",
"description": "Binary Object Representation Serializer for Hashing",
"main": "./lib/cjs/index.js",
"module": "./lib/esm/index.js",