Skip to content

Commit dc24384

Browse files
authored
Merge pull request #64 from msgpack/sort_keys
Support canonical encoding by adding sortKeys option to encode()
2 parents aed070f + 3c0ee99 commit dc24384

File tree

4 files changed

+35
-17
lines changed

4 files changed

+35
-17
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@ npm install @msgpack/msgpack
4646

4747
It encodes `data` and returns a byte array as `Uint8Array`.
4848

49+
#### EncodeOptions
50+
51+
Name|Type|Default
52+
----|----|----
53+
extensionCodec | ExtensionCodec | `ExtensinCodec.defaultCodec`
54+
maxDepth | number | `100`
55+
initialBufferSize | number | `2048`
56+
sortKeys | boolean | false
57+
4958
### `decode(buffer: ArrayLike<number>, options?: DecodeOptions): unknown`
5059

5160
It decodes `buffer` encoded as MessagePack, and returns a decoded object as `uknown`.

src/Encoder.ts

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export class Encoder {
1717
readonly extensionCodec = ExtensionCodec.defaultCodec,
1818
readonly maxDepth = DEFAULT_MAX_DEPTH,
1919
readonly initialBufferSize = DEFAULT_INITIAL_BUFFER_SIZE,
20+
readonly sortKeys = false,
2021
) {}
2122

2223
encode(object: unknown, depth: number): void {
@@ -223,18 +224,12 @@ export class Encoder {
223224
}
224225
}
225226

226-
countObjectKeys(object: Record<string, unknown>): number {
227-
let count = 0;
228-
for (const key in object) {
229-
if (Object.prototype.hasOwnProperty.call(object, key)) {
230-
count++;
231-
}
232-
}
233-
return count;
234-
}
235-
236227
encodeMap(object: Record<string, unknown>, depth: number) {
237-
const size = this.countObjectKeys(object);
228+
const keys = Object.keys(object);
229+
if (this.sortKeys) {
230+
keys.sort();
231+
}
232+
const size = keys.length;
238233
if (size < 16) {
239234
// fixmap
240235
this.writeU8(0x80 + size);
@@ -249,11 +244,11 @@ export class Encoder {
249244
} else {
250245
throw new Error(`Too large map object: ${size}`);
251246
}
252-
for (const key in object) {
253-
if (Object.prototype.hasOwnProperty.call(object, key)) {
254-
this.encodeString(key);
255-
this.encode(object[key], depth + 1);
256-
}
247+
248+
for (let i = 0; i < size; i++) {
249+
const key = keys[i];
250+
this.encodeString(key);
251+
this.encode(object[key], depth + 1);
257252
}
258253
}
259254

src/encode.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ export type EncodeOptions = Partial<
66
extensionCodec: ExtensionCodecType;
77
maxDepth: number;
88
initialBufferSize: number;
9+
sortKeys: boolean;
910
}>
1011
>;
1112

1213
const defaultEncodeOptions = {};
1314

1415
export function encode(value: unknown, options: EncodeOptions = defaultEncodeOptions): Uint8Array {
15-
const encoder = new Encoder(options.extensionCodec, options.maxDepth, options.initialBufferSize);
16+
const encoder = new Encoder(options.extensionCodec, options.maxDepth, options.initialBufferSize, options.sortKeys);
1617
encoder.encode(value, 1);
1718
return encoder.getUint8Array();
1819
}

test/encode-options.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import assert from "assert";
2+
import { encode } from "@msgpack/msgpack";
3+
4+
describe("encode options", () => {
5+
context("sortKeys", () => {
6+
it("canonicalize encoded binaries", () => {
7+
assert.deepStrictEqual(
8+
encode({ a: 1, b: 2 }, { sortKeys: true }),
9+
encode({ b: 2, a: 1 }, { sortKeys: true }),
10+
);
11+
});
12+
});
13+
});

0 commit comments

Comments
 (0)