Skip to content

Commit

Permalink
Add support for read/write functions for easier/faster custom extensi…
Browse files Browse the repository at this point in the history
…ons, #26
  • Loading branch information
kriszyp committed May 12, 2021
1 parent 7c85c15 commit 539aee5
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 11 deletions.
29 changes: 26 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ msgpack.Decoder().on("data",ondata).decode(buf); | 1000000 | 2246 | 445235
See the [benchmark.md](benchmark.md) for more benchmarks and information about benchmarking.

## Custom Extensions
You can add your own custom extensions, which can be used to encode specific types/classes in certain ways. This is done by using the `addExtension` function, and specifying the class, extension type code (should be a number from 1-100, reserving negatives for MessagePack, 101-127 for msgpackr), and your pack and unpack functions (or just the one you need). You can use msgpackr encoding and decoding within your extensions, but if you do so, you must create a separate Packr instance, otherwise you could override data in the same encoding buffer:
You can add your own custom extensions, which can be used to encode specific types/classes in certain ways. This is done by using the `addExtension` function, and specifying the class, extension `type` code (should be a number from 1-100, reserving negatives for MessagePack, 101-127 for msgpackr), and your `pack` and `unpack` functions (or just the one you need).
```js
import { addExtension, Packr } from 'msgpackr';

Expand All @@ -216,16 +216,39 @@ addExtension({
type: 11, // register your own extension code (a type code from 1-100)
pack(instance) {
// define how your custom class should be encoded
return extPackr.pack(instance.myData); // return a buffer
return Buffer.from([instance.myData]); // return a buffer
}
unpack(buffer) {
// define how your custom class should be decoded
let instance = new MyCustomClass();
instance.myData = extPackr.unpack(buffer);
instance.myData = buffer[0];
return instance; // decoded value from buffer
}
});
```
If you want to use msgpackr to encode and decode the data within your extensions, you can use the `read` and `write` functions and read and write data/objects that will be encoded and decoded by msgpackr, which can be easier and faster than creating and receiving separate buffers (note that you can't just return the instance from `write` or msgpackr will recursively try to use extension infinitely):
```js
import { addExtension, Packr } from 'msgpackr';

class MyCustomClass {...}

let extPackr = new Packr();
addExtension({
Class: MyCustomClass,
type: 11, // register your own extension code (a type code from 1-100)
write(instance) {
// define how your custom class should be encoded
return instance.myData; // return some data to be encoded
}
read(data) {
// define how your custom class should be decoded,
// data will already be unpacked/decoded
let instance = new MyCustomClass();
instance.myData = data;
return instance; // return decoded value
}
});
```

### Additional Performance Optimizations
Msgpackr is already fast, but here are some tips for making it faster:
Expand Down
11 changes: 9 additions & 2 deletions pack.js
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,13 @@ class Packr extends Unpackr {
let extensionClass = extensionClasses[i]
if (value instanceof extensionClass) {
let extension = extensions[i]
if (extension.write) {
target[position++] = 0xd4 // one byte "tag" extension
target[position++] = extension.type
target[position++] = 0
pack(extension.write.call(this, value))
return
}
let currentTarget = target
let currentTargetView = targetView
let currentPosition = position
Expand Down Expand Up @@ -748,8 +755,8 @@ function insertIds(serialized, idsToInsert) {

exports.addExtension = function(extension) {
if (extension.Class) {
if (!extension.pack)
throw new Error('Extension has no pack function')
if (!extension.pack && !extension.write)
throw new Error('Extension has no pack or write function')
extensionClasses.unshift(extension.Class)
extensions.unshift(extension)
}
Expand Down
41 changes: 39 additions & 2 deletions tests/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ suite('msgpackr basic tests', function(){
assert.deepEqual(deserialized2, data2)
})

test('extended class', function(){
test('extended class pack/unpack', function(){
function Extended() {

}
Expand Down Expand Up @@ -181,7 +181,7 @@ suite('msgpackr basic tests', function(){
assert.deepEqual(data, deserialized)
assert.equal(deserialized.extendedInstance.getDouble(), 8)
})
test('extended class', function(){
test('extended class pack/unpack custom size', function(){
function TestClass() {

}
Expand All @@ -198,6 +198,43 @@ suite('msgpackr basic tests', function(){
let result = unpack(pack(new TestClass()));
assert.equal(result, 256)
})

test('extended class read/write', function(){
function Extended() {

}
Extended.prototype.getDouble = function() {
return this.value * 2
}
var instance = new Extended()
instance.value = 4
instance.string = 'decode this: ᾜ'
var data = {
prop1: 'has multi-byte: ᾜ',
extendedInstance: instance,
prop2: 'more string',
num: 3,
}
let packr = new Packr()
addExtension({
Class: Extended,
type: 12,
read: function(data) {
let e = new Extended()
e.value = data[0]
e.string = data[1]
return e
},
write: function(instance) {
return [instance.value, instance.string]
}
})
var serialized = pack(data)
var deserialized = unpack(serialized)
assert.deepEqual(data, deserialized)
assert.equal(deserialized.extendedInstance.getDouble(), 8)
})

test.skip('text decoder', function() {
let td = new TextDecoder('ISO-8859-15')
let b = Buffer.alloc(3)
Expand Down
16 changes: 12 additions & 4 deletions unpack.js
Original file line number Diff line number Diff line change
Expand Up @@ -280,9 +280,14 @@ function read() {
if (value == 0x72) {
return recordDefinition(src[position++])
} else {
if (currentExtensions[value])
return currentExtensions[value](src.subarray(position, ++position))
else
let extension = currentExtensions[value]
if (extension) {
if (extension.read) {
position++ // skip filler byte
return extension.read(read())
} else
return extension(src.subarray(position, ++position))
} else
throw new Error('Unknown extension ' + value)
}
case 0xd5:
Expand Down Expand Up @@ -839,7 +844,10 @@ function clearSource() {
}

exports.addExtension = function(extension) {
currentExtensions[extension.type] = extension.unpack
if (extension.unpack)
currentExtensions[extension.type] = extension.unpack
else
currentExtensions[extension.type] = extension
}

let mult10 = new Array(147) // this is a table matching binary exponents to the multiplier to determine significant digit rounding
Expand Down

0 comments on commit 539aee5

Please sign in to comment.