From 2d54ad1733423f843f1d7c40c3a84f15638c1016 Mon Sep 17 00:00:00 2001 From: "FUJI Goro (gfx)" Date: Tue, 11 Jun 2019 23:26:37 +0900 Subject: [PATCH 1/2] add sortKeys option to encode() --- README.md | 9 +++++++++ src/Encoder.ts | 17 +++++++++++------ src/encode.ts | 3 ++- test/encode-options.test.ts | 13 +++++++++++++ 4 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 test/encode-options.test.ts diff --git a/README.md b/README.md index deba004e..9dbd4031 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,15 @@ npm install @msgpack/msgpack It encodes `data` and returns a byte array as `Uint8Array`. +#### EncodeOptions + +Name|Type|Default +----|----|---- +extensionCodec | ExtensionCodec | `ExtensinCodec.defaultCodec` +maxDepth | number | `100` +initialBufferSize | number | `2048` +sortKeys | boolean | false + ### `decode(buffer: ArrayLike, options?: DecodeOptions): unknown` It decodes `buffer` encoded as MessagePack, and returns a decoded object as `uknown`. diff --git a/src/Encoder.ts b/src/Encoder.ts index b0636a7f..e9bbf930 100644 --- a/src/Encoder.ts +++ b/src/Encoder.ts @@ -17,6 +17,7 @@ export class Encoder { readonly extensionCodec = ExtensionCodec.defaultCodec, readonly maxDepth = DEFAULT_MAX_DEPTH, readonly initialBufferSize = DEFAULT_INITIAL_BUFFER_SIZE, + readonly sortKeys = false, ) {} encode(object: unknown, depth: number): void { @@ -234,7 +235,11 @@ export class Encoder { } encodeMap(object: Record, depth: number) { - const size = this.countObjectKeys(object); + const keys = Object.keys(object); + if (this.sortKeys) { + keys.sort(); + } + const size = keys.length; if (size < 16) { // fixmap this.writeU8(0x80 + size); @@ -249,11 +254,11 @@ export class Encoder { } else { throw new Error(`Too large map object: ${size}`); } - for (const key in object) { - if (Object.prototype.hasOwnProperty.call(object, key)) { - this.encodeString(key); - this.encode(object[key], depth + 1); - } + + for (let i = 0; i < size; i++) { + const key = keys[i]; + this.encodeString(key); + this.encode(object[key], depth + 1); } } diff --git a/src/encode.ts b/src/encode.ts index 6bd6d761..ad9030b9 100644 --- a/src/encode.ts +++ b/src/encode.ts @@ -6,13 +6,14 @@ export type EncodeOptions = Partial< extensionCodec: ExtensionCodecType; maxDepth: number; initialBufferSize: number; + sortKeys: boolean; }> >; const defaultEncodeOptions = {}; export function encode(value: unknown, options: EncodeOptions = defaultEncodeOptions): Uint8Array { - const encoder = new Encoder(options.extensionCodec, options.maxDepth, options.initialBufferSize); + const encoder = new Encoder(options.extensionCodec, options.maxDepth, options.initialBufferSize, options.sortKeys); encoder.encode(value, 1); return encoder.getUint8Array(); } diff --git a/test/encode-options.test.ts b/test/encode-options.test.ts new file mode 100644 index 00000000..e4910729 --- /dev/null +++ b/test/encode-options.test.ts @@ -0,0 +1,13 @@ +import assert from "assert"; +import { encode } from "@msgpack/msgpack"; + +describe("encode options", () => { + context("sortKeys", () => { + it("canonicalize encoded binaries", () => { + assert.deepStrictEqual( + encode({ a: 1, b: 2 }, { sortKeys: true }), + encode({ b: 2, a: 1 }, { sortKeys: true }), + ); + }); + }); +}); From 3c0ee99cd4b240842766be5e61b7439d737d7d45 Mon Sep 17 00:00:00 2001 From: "FUJI Goro (gfx)" Date: Tue, 11 Jun 2019 23:33:29 +0900 Subject: [PATCH 2/2] remove unused utility --- src/Encoder.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/Encoder.ts b/src/Encoder.ts index e9bbf930..fd9b653c 100644 --- a/src/Encoder.ts +++ b/src/Encoder.ts @@ -224,16 +224,6 @@ export class Encoder { } } - countObjectKeys(object: Record): number { - let count = 0; - for (const key in object) { - if (Object.prototype.hasOwnProperty.call(object, key)) { - count++; - } - } - return count; - } - encodeMap(object: Record, depth: number) { const keys = Object.keys(object); if (this.sortKeys) {