Skip to content

Commit

Permalink
feat: typed deserialization
Browse files Browse the repository at this point in the history
Currently the outter deserialization returns a `DecodeType` that we
expect users to cast to their own type `T`. This change modifies the
interface from `deserialize(...): DecodeType` to an automatic casting
`deserialize<T>(...): T`.

For JS users this makes no difference, since there are no types involved
anyway.

For TS users this is a **breaking** change, since `deserialize` will now
return an `unknown` type instead of a `DecodeType` by default if no `T`
is given. However, we would expect users to always be want to cast to `T`.
  • Loading branch information
gagdiez committed Nov 21, 2023
1 parent ccf0b00 commit 081c562
Show file tree
Hide file tree
Showing 33 changed files with 117 additions and 66 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: bool = true): Uint8Array` - serializes an object `obj` according to the schema `schema`. If the optional parameter `validate` is set to false, no validation of the `schema` will be made against the object.
- `deserialize<T>(schema: Schema, buffer: Uint8Array, validate: bool = true): T` - deserializes an object according to the schema `schema` from the buffer `buffer`. If the optional parameter `validate` is set to false, no validation of the `schema` will be made against the object.

## Schemas
Schemas are used to describe the structure of the data being serialized or deserialized. They are used to
Expand Down
6 changes: 6 additions & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
presets: [
['@babel/preset-env', { targets: { node: 'current' } }],
'@babel/preset-typescript',
],
};
2 changes: 1 addition & 1 deletion borsh-ts/buffer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IntegerType } from './types.js';
import { IntegerType } from './types';

export class EncodeBuffer {
offset: number;
Expand Down
4 changes: 2 additions & 2 deletions borsh-ts/deserialize.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ArrayType, DecodeTypes, MapType, IntegerType, OptionType, Schema, SetType, StructType, integers, EnumType } from './types.js';
import { DecodeBuffer } from './buffer.js';
import { ArrayType, DecodeTypes, MapType, IntegerType, OptionType, Schema, SetType, StructType, integers, EnumType } from './types';
import { DecodeBuffer } from './buffer';

export class BorshDeserializer {
buffer: DecodeBuffer;
Expand Down
12 changes: 6 additions & 6 deletions borsh-ts/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Schema, DecodeTypes } from './types.js';
import { BorshSerializer } from './serialize.js';
import { BorshDeserializer } from './deserialize.js';
import * as utils from './utils.js';
import { Schema } from './types';
import { BorshSerializer } from './serialize';
import { BorshDeserializer } from './deserialize';
import * as utils from './utils';

export { Schema } from './types';

Expand All @@ -11,8 +11,8 @@ export function serialize(schema: Schema, value: unknown, validate = true): Uint
return serializer.encode(value, schema);
}

export function deserialize(schema: Schema, buffer: Uint8Array, validate = true): DecodeTypes {
export function deserialize<T>(schema: Schema, buffer: Uint8Array, validate = true): T {
if (validate) utils.validate_schema(schema);
const deserializer = new BorshDeserializer(buffer);
return deserializer.decode(schema);
return deserializer.decode(schema) as T;
}
6 changes: 3 additions & 3 deletions borsh-ts/serialize.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ArrayType, MapType, IntegerType, OptionType, Schema, SetType, StructType, integers, EnumType } from './types.js';
import { EncodeBuffer } from './buffer.js';
import * as utils from './utils.js';
import { ArrayType, MapType, IntegerType, OptionType, Schema, SetType, StructType, integers, EnumType } from './types';
import { EncodeBuffer } from './buffer';
import * as utils from './utils';

export class BorshSerializer {
encoded: EncodeBuffer;
Expand Down
16 changes: 16 additions & 0 deletions borsh-ts/test/(de)serialize.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,20 @@ test('errors on invalid values', async () => {
expect(() => check_encode(3, 'string', [])).toThrow('Expected string not number(3) at value');
expect(() => check_encode({ 'a': 1, 'b': '2' }, { struct: { a: 'u8', b: 'u8' } }, [])).toThrow('Expected number not string(2) at value.b');
expect(() => check_encode({ 'a': { 'b': { 'c': 3 } } }, { struct: { a: { struct: { b: { struct: { c: 'string' } } } } } }, [])).toThrow('Expected string not number(3) at value.a.b.c');
});

test('(de)serialize follows the schema order', async () => {
let data = { value: 'test' };

const MyTypeSchema = {
struct: {
value: 'string'
}
};

const serialized = borsh.serialize(MyTypeSchema, data);

// No explicit type
const myTypeInstance_1 = borsh.deserialize(MyTypeSchema, serialized);
expect(myTypeInstance_1.value).toBe('test');
});
24 changes: 24 additions & 0 deletions borsh-ts/test/(de)serialize.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Schema } from '../types';
import * as borsh from '../../lib/esm/index';

type MyType = {
value: string;
};

test('deserialize accepts a type cast', async () => {
const data = { value: 'test' };

const MyTypeSchema: Schema = {
struct: {
value: 'string'
}
};

const serialized = borsh.serialize(MyTypeSchema, data);

const deserialized = borsh.deserialize<MyType>(MyTypeSchema, serialized);
expect(deserialized.value).toBe('test');

const deserialized_2 = <MyType>borsh.deserialize(MyTypeSchema, serialized);
expect(deserialized_2.value).toBe('test');
});
2 changes: 1 addition & 1 deletion borsh-ts/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Schema, StructType, integers } from './types.js';
import { Schema, StructType, integers } from './types';

export function isArrayLike(value: unknown): boolean {
// source: https://stackoverflow.com/questions/24048547/checking-if-an-object-is-array-like
Expand Down
2 changes: 1 addition & 1 deletion lib/cjs/buffer.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IntegerType } from './types.js';
import { IntegerType } from './types';
export declare class EncodeBuffer {
offset: number;
buffer_size: number;
Expand Down
4 changes: 2 additions & 2 deletions lib/cjs/deserialize.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ArrayType, DecodeTypes, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types.js';
import { DecodeBuffer } from './buffer.js';
import { ArrayType, DecodeTypes, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types';
import { DecodeBuffer } from './buffer';
export declare class BorshDeserializer {
buffer: DecodeBuffer;
constructor(bufferArray: Uint8Array);
Expand Down
8 changes: 4 additions & 4 deletions lib/cjs/deserialize.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
"use strict";
exports.__esModule = true;
exports.BorshDeserializer = void 0;
var types_js_1 = require("./types.js");
var buffer_js_1 = require("./buffer.js");
var types_1 = require("./types");
var buffer_1 = require("./buffer");
var BorshDeserializer = /** @class */ (function () {
function BorshDeserializer(bufferArray) {
this.buffer = new buffer_js_1.DecodeBuffer(bufferArray);
this.buffer = new buffer_1.DecodeBuffer(bufferArray);
}
BorshDeserializer.prototype.decode = function (schema) {
return this.decode_value(schema);
};
BorshDeserializer.prototype.decode_value = function (schema) {
if (typeof schema === 'string') {
if (types_js_1.integers.includes(schema))
if (types_1.integers.includes(schema))
return this.decode_integer(schema);
if (schema === 'string')
return this.decode_string();
Expand Down
4 changes: 2 additions & 2 deletions lib/cjs/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Schema, DecodeTypes } from './types.js';
import { Schema } from './types';
export { Schema } from './types';
export declare function serialize(schema: Schema, value: unknown, validate?: boolean): Uint8Array;
export declare function deserialize(schema: Schema, buffer: Uint8Array, validate?: boolean): DecodeTypes;
export declare function deserialize<T>(schema: Schema, buffer: Uint8Array, validate?: boolean): T;
10 changes: 5 additions & 5 deletions lib/cjs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,22 @@ var __importStar = (this && this.__importStar) || function (mod) {
};
exports.__esModule = true;
exports.deserialize = exports.serialize = void 0;
var serialize_js_1 = require("./serialize.js");
var deserialize_js_1 = require("./deserialize.js");
var utils = __importStar(require("./utils.js"));
var serialize_1 = require("./serialize");
var deserialize_1 = require("./deserialize");
var utils = __importStar(require("./utils"));
function serialize(schema, value, validate) {
if (validate === void 0) { validate = true; }
if (validate)
utils.validate_schema(schema);
var serializer = new serialize_js_1.BorshSerializer(validate);
var serializer = new serialize_1.BorshSerializer(validate);
return serializer.encode(value, schema);
}
exports.serialize = serialize;
function deserialize(schema, buffer, validate) {
if (validate === void 0) { validate = true; }
if (validate)
utils.validate_schema(schema);
var deserializer = new deserialize_js_1.BorshDeserializer(buffer);
var deserializer = new deserialize_1.BorshDeserializer(buffer);
return deserializer.decode(schema);
}
exports.deserialize = deserialize;
4 changes: 2 additions & 2 deletions lib/cjs/serialize.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ArrayType, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types.js';
import { EncodeBuffer } from './buffer.js';
import { ArrayType, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types';
import { EncodeBuffer } from './buffer';
export declare class BorshSerializer {
encoded: EncodeBuffer;
fieldPath: string[];
Expand Down
10 changes: 5 additions & 5 deletions lib/cjs/serialize.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ var __importStar = (this && this.__importStar) || function (mod) {
};
exports.__esModule = true;
exports.BorshSerializer = void 0;
var types_js_1 = require("./types.js");
var buffer_js_1 = require("./buffer.js");
var utils = __importStar(require("./utils.js"));
var types_1 = require("./types");
var buffer_1 = require("./buffer");
var utils = __importStar(require("./utils"));
var BorshSerializer = /** @class */ (function () {
function BorshSerializer(checkTypes) {
this.encoded = new buffer_js_1.EncodeBuffer();
this.encoded = new buffer_1.EncodeBuffer();
this.fieldPath = ['value'];
this.checkTypes = checkTypes;
}
Expand All @@ -39,7 +39,7 @@ var BorshSerializer = /** @class */ (function () {
};
BorshSerializer.prototype.encode_value = function (value, schema) {
if (typeof schema === 'string') {
if (types_js_1.integers.includes(schema))
if (types_1.integers.includes(schema))
return this.encode_integer(value, schema);
if (schema === 'string')
return this.encode_string(value);
Expand Down
2 changes: 1 addition & 1 deletion lib/cjs/utils.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Schema } from './types.js';
import { Schema } from './types';
export declare function isArrayLike(value: unknown): boolean;
export declare function expect_type(value: unknown, type: string, fieldPath: string[]): void;
export declare function expect_bigint(value: unknown, fieldPath: string[]): void;
Expand Down
4 changes: 2 additions & 2 deletions lib/cjs/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var __extends = (this && this.__extends) || (function () {
})();
exports.__esModule = true;
exports.validate_schema = exports.ErrorSchema = exports.expect_enum = exports.expect_same_size = exports.expect_bigint = exports.expect_type = exports.isArrayLike = void 0;
var types_js_1 = require("./types.js");
var types_1 = require("./types");
function isArrayLike(value) {
// source: https://stackoverflow.com/questions/24048547/checking-if-an-object-is-array-like
return (Array.isArray(value) ||
Expand Down Expand Up @@ -56,7 +56,7 @@ function expect_enum(value, fieldPath) {
}
exports.expect_enum = expect_enum;
// Validate Schema
var VALID_STRING_TYPES = types_js_1.integers.concat(['bool', 'string']);
var VALID_STRING_TYPES = types_1.integers.concat(['bool', 'string']);
var VALID_OBJECT_KEYS = ['option', 'enum', 'array', 'set', 'map', 'struct'];
var ErrorSchema = /** @class */ (function (_super) {
__extends(ErrorSchema, _super);
Expand Down
2 changes: 1 addition & 1 deletion lib/esm/buffer.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IntegerType } from './types.js';
import { IntegerType } from './types';
export declare class EncodeBuffer {
offset: number;
buffer_size: number;
Expand Down
4 changes: 2 additions & 2 deletions lib/esm/deserialize.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ArrayType, DecodeTypes, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types.js';
import { DecodeBuffer } from './buffer.js';
import { ArrayType, DecodeTypes, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types';
import { DecodeBuffer } from './buffer';
export declare class BorshDeserializer {
buffer: DecodeBuffer;
constructor(bufferArray: Uint8Array);
Expand Down
4 changes: 2 additions & 2 deletions lib/esm/deserialize.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { integers } from './types.js';
import { DecodeBuffer } from './buffer.js';
import { integers } from './types';
import { DecodeBuffer } from './buffer';
var BorshDeserializer = /** @class */ (function () {
function BorshDeserializer(bufferArray) {
this.buffer = new DecodeBuffer(bufferArray);
Expand Down
4 changes: 2 additions & 2 deletions lib/esm/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Schema, DecodeTypes } from './types.js';
import { Schema } from './types';
export { Schema } from './types';
export declare function serialize(schema: Schema, value: unknown, validate?: boolean): Uint8Array;
export declare function deserialize(schema: Schema, buffer: Uint8Array, validate?: boolean): DecodeTypes;
export declare function deserialize<T>(schema: Schema, buffer: Uint8Array, validate?: boolean): T;
6 changes: 3 additions & 3 deletions lib/esm/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BorshSerializer } from './serialize.js';
import { BorshDeserializer } from './deserialize.js';
import * as utils from './utils.js';
import { BorshSerializer } from './serialize';
import { BorshDeserializer } from './deserialize';
import * as utils from './utils';
export function serialize(schema, value, validate) {
if (validate === void 0) { validate = true; }
if (validate)
Expand Down
4 changes: 2 additions & 2 deletions lib/esm/serialize.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ArrayType, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types.js';
import { EncodeBuffer } from './buffer.js';
import { ArrayType, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types';
import { EncodeBuffer } from './buffer';
export declare class BorshSerializer {
encoded: EncodeBuffer;
fieldPath: string[];
Expand Down
6 changes: 3 additions & 3 deletions lib/esm/serialize.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { integers } from './types.js';
import { EncodeBuffer } from './buffer.js';
import * as utils from './utils.js';
import { integers } from './types';
import { EncodeBuffer } from './buffer';
import * as utils from './utils';
var BorshSerializer = /** @class */ (function () {
function BorshSerializer(checkTypes) {
this.encoded = new EncodeBuffer();
Expand Down
2 changes: 1 addition & 1 deletion lib/esm/utils.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Schema } from './types.js';
import { Schema } from './types';
export declare function isArrayLike(value: unknown): boolean;
export declare function expect_type(value: unknown, type: string, fieldPath: string[]): void;
export declare function expect_bigint(value: unknown, fieldPath: string[]): void;
Expand Down
2 changes: 1 addition & 1 deletion lib/esm/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ var __extends = (this && this.__extends) || (function () {
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
import { integers } from './types.js';
import { integers } from './types';
export function isArrayLike(value) {
// source: https://stackoverflow.com/questions/24048547/checking-if-an-object-is-array-like
return (Array.isArray(value) ||
Expand Down
2 changes: 1 addition & 1 deletion lib/types/buffer.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IntegerType } from './types.js';
import { IntegerType } from './types';
export declare class EncodeBuffer {
offset: number;
buffer_size: number;
Expand Down
4 changes: 2 additions & 2 deletions lib/types/deserialize.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ArrayType, DecodeTypes, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types.js';
import { DecodeBuffer } from './buffer.js';
import { ArrayType, DecodeTypes, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types';
import { DecodeBuffer } from './buffer';
export declare class BorshDeserializer {
buffer: DecodeBuffer;
constructor(bufferArray: Uint8Array);
Expand Down
4 changes: 2 additions & 2 deletions lib/types/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Schema, DecodeTypes } from './types.js';
import { Schema } from './types';
export { Schema } from './types';
export declare function serialize(schema: Schema, value: unknown, validate?: boolean): Uint8Array;
export declare function deserialize(schema: Schema, buffer: Uint8Array, validate?: boolean): DecodeTypes;
export declare function deserialize<T>(schema: Schema, buffer: Uint8Array, validate?: boolean): T;
4 changes: 2 additions & 2 deletions lib/types/serialize.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ArrayType, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types.js';
import { EncodeBuffer } from './buffer.js';
import { ArrayType, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types';
import { EncodeBuffer } from './buffer';
export declare class BorshSerializer {
encoded: EncodeBuffer;
fieldPath: string[];
Expand Down
2 changes: 1 addition & 1 deletion lib/types/utils.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Schema } from './types.js';
import { Schema } from './types';
export declare function isArrayLike(value: unknown): boolean;
export declare function expect_type(value: unknown, type: string, fieldPath: string[]): void;
export declare function expect_bigint(value: unknown, fieldPath: string[]): void;
Expand Down
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,19 @@
},
"homepage": "https://github.com/near/borsh-js#readme",
"devDependencies": {
"@babel/core": "^7.23.3",
"@babel/preset-env": "^7.23.3",
"@babel/preset-typescript": "^7.23.3",
"@types/babel__core": "^7.1.2",
"@types/babel__template": "^7.0.2",
"@types/jest": "^29.5.9",
"@types/node": "^12.7.3",
"@typescript-eslint/eslint-plugin": "^5.28.0",
"@typescript-eslint/parser": "^5.28.0",
"babel-jest": "^29.7.0",
"bn.js": "^5.2.0",
"eslint": "^8.17.0",
"jest": "^26.0.1",
"typescript": "^4",
"bn.js": "^5.2.0"
"typescript": "^4"
}
}

0 comments on commit 081c562

Please sign in to comment.