Skip to content

Commit

Permalink
support encoding of native Map object types
Browse files Browse the repository at this point in the history
this supports the ES2015 Map object as a msgpack `fixmap`,
`map16`, or `map32`.

Some tests "documenting" the oddities of this implementation have
been added as well to ensure consistency of the oddities are kept.
  • Loading branch information
imnotjames committed May 16, 2018
1 parent 005eceb commit a41ec94
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 0 deletions.
55 changes: 55 additions & 0 deletions lib/encoder.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ module.exports = function buildEncode (encodingTypes, forceFloat64, compatibilit
}, bl().append(buf))
} else if (!disableTimestampEncoding && typeof obj.getDate === 'function') {
return encodeDate(obj)
} else if (typeof Map !== 'undefined' && obj instanceof Map) {
return encodeMap(obj)
} else if (typeof obj === 'object') {
buf = encodeExt(obj) || encodeObject(obj)
} else if (typeof obj === 'number') {
Expand Down Expand Up @@ -229,6 +231,59 @@ module.exports = function buildEncode (encodingTypes, forceFloat64, compatibilit
return bl().append(Buffer.from(headers)).append(encoded)
}

function encodeMap (obj) {
let buffers = []
let length = 0

// Pack up all of the key / value pairs
// and keep track of the ongoing length
// that we've packed so far.
for (let [key, value] of obj.entries()) {
buffers.push(encode(key, true))
buffers.push(encode(value))
length++
}

let header

// There are three types of maps
//
// * fixedmap (header byte 0x80 - 0x8F)
// * map16 (header byte 0xde)
// * map32 (header byte 0xdf)
//
// For further information on the map types,
// see: https://github.com/msgpack/msgpack/blob/master/spec.md#map-format-family

if (length < 16) {
// fixedmap fits up to 15 items in it.
// The header is only a single byte of
// 0x80 + the size.
header = Buffer.allocUnsafe(1)
header.writeUInt8(0x80 | length)
} else if (length < 0xFFFF) {
// map16 fits up to 65535 items in it.
// The header is a 0xde header byte
// and a 16 bit unsigned (2 bytes)
header = Buffer.allocUnsafe(3)
header.writeUInt8(0xde)
header.writeUInt16BE(length, 1)
} else {
// map32 fits up to 4294967295 items in it
// The header is a 0xdf header byte
// and a 32 bit unsigned (4 bytes)
header = Buffer.allocUnsafe(5)
header.writeUInt8(0xdf)
header.writeUInt32BE(length, 1)
}

// Combine all of the buffers together (in the right order)
// to create the output buffer we want.
buffers.unshift(header)

return Buffer.concat(buffers)
}

function encodeObject (obj) {
var acc = []
var length = 0
Expand Down
66 changes: 66 additions & 0 deletions test/map.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
'use strict'

var test = require('tape').test
var msgpack = require('../')

test('decoding a native Map object', function (t) {
var map = new Map([
[ 'hello', 'world' ]
])

var expected = { hello: 'world' }

var pack = msgpack()

t.deepEqual(pack.decode(pack.encode(map)), expected)
t.end()
})

test('decoding a native Map object with Map objects inside it', function (t) {
var map = new Map([
[ 'hello', 'world' ],
[ 'foo', new Map([[ 'bar', 'baz' ]]) ]
])

var expected = { 'hello': 'world', 'foo': { 'bar': 'baz' } }

var pack = msgpack()

t.deepEqual(pack.decode(pack.encode(map)), expected)
t.end()
})

// The decoding done in this test is not exactly correct..
// The key is decoded as a `string` rather than a `number`, but
// the test has been added to preserve consistency.
test('decoding a native Map object with a numeric key', function (t) {
var map = new Map([
[ 1, 'world' ]
])

var obj = { '1': 'world' }

var pack = msgpack()

t.deepEqual(pack.decode(pack.encode(map)), obj)
t.end()
})

// The decoding done in this next test is DEFINITELY not correct!
// The two keys are encoded as two different things but are decoded
// as one key, causing an overwrite.
// Again, this test has been added to preserve consistency!
test('decoding a native Map object with a numeric key and a string number representation', function (t) {
var map = new Map([
[ 1, 'world' ],
[ '1', 'hello' ]
])

var obj = { '1': 'hello' }

var pack = msgpack()

t.notEqual(pack.encode(map), pack.encode(obj))
t.deepEqual(pack.decode(pack.encode(map)), obj)
t.end()
})

0 comments on commit a41ec94

Please sign in to comment.