Skip to content

Commit

Permalink
Merge pull request #107 from msgpack/keep_undefined
Browse files Browse the repository at this point in the history
add EncodeOptions#ignoreUndefined to ignore undefined values in maps
  • Loading branch information
gfx authored Mar 3, 2020
2 parents c98fd4e + e0d17b9 commit 27e0ebb
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 5 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ maxDepth | number | `100`
initialBufferSize | number | `2048`
sortKeys | boolean | false
forceFloat32 | boolean | false
ignoreUndefined | boolean | false
context | user-defined | -

### `decode(buffer: ArrayLike<number> | ArrayBuffer, options?: DecodeOptions): unknown`
Expand Down
28 changes: 23 additions & 5 deletions src/Encoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export class Encoder<ContextType> {
readonly initialBufferSize = DEFAULT_INITIAL_BUFFER_SIZE,
readonly sortKeys = false,
readonly forceFloat32 = false,
readonly ignoreUndefined = false,
) {}

encode(object: unknown, depth: number): void {
Expand Down Expand Up @@ -231,12 +232,26 @@ export class Encoder<ContextType> {
}
}

countWithoutUndefined(object: Record<string, unknown>, keys: ReadonlyArray<string>): number {
let count = 0;

for (const key of keys) {
if (object[key] !== undefined) {
count++;
}
}

return count;
}

encodeMap(object: Record<string, unknown>, depth: number) {
const keys = Object.keys(object);
if (this.sortKeys) {
keys.sort();
}
const size = keys.length;

const size = this.ignoreUndefined ? this.countWithoutUndefined(object, keys) : keys.length;

if (size < 16) {
// fixmap
this.writeU8(0x80 + size);
Expand All @@ -252,10 +267,13 @@ export class Encoder<ContextType> {
throw new Error(`Too large map object: ${size}`);
}

for (let i = 0; i < size; i++) {
const key = keys[i];
this.encodeString(key);
this.encode(object[key], depth + 1);
for (const key of keys) {
const value = object[key];

if (!(this.ignoreUndefined && value === undefined)) {
this.encodeString(key);
this.encode(value, depth + 1);
}
}
}

Expand Down
9 changes: 9 additions & 0 deletions src/encode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ export type EncodeOptions<ContextType = undefined> = Partial<
* Only use it if precisions don't matter.
*/
forceFloat32: boolean;

/**
* If `true`, an object property with `undefined` value are ignored.
* e.g. `{ foo: undefined }` will be encoded as `{}`, as `JSON.stringify()` does.
*
* The default is `false`. Note that it needs more time to encode.
*/
ignoreUndefined: boolean;
}>
> &
ContextOf<ContextType>;
Expand All @@ -38,6 +46,7 @@ export function encode<ContextType>(
options.initialBufferSize,
options.sortKeys,
options.forceFloat32,
options.ignoreUndefined,
);
encoder.encode(value, 1);
return encoder.getUint8Array();
Expand Down
17 changes: 17 additions & 0 deletions test/encode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,23 @@ describe("encode", () => {
});
});

context("ignoreUndefined", () => {
it("encodes { foo: undefined } as is by default", () => {
assert.deepStrictEqual(decode(encode({ foo: undefined, bar: 42 })), { foo: null, bar: 42 });
});

it("encodes { foo: undefined } as is with `ignoreUndefined: false`", () => {
assert.deepStrictEqual(decode(encode({ foo: undefined, bar: 42 }, { ignoreUndefined: false })), {
foo: null,
bar: 42,
});
});

it("encodes { foo: undefined } to {} with `ignoreUndefined: true`", () => {
assert.deepStrictEqual(decode(encode({ foo: undefined, bar: 42 }, { ignoreUndefined: true })), { bar: 42 });
});
});

context("ArrayBuffer as buffer", () => {
const buffer = encode([1, 2, 3]);
const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteLength);
Expand Down

0 comments on commit 27e0ebb

Please sign in to comment.