Skip to content

Commit 7ad7809

Browse files
authored
Merge pull request #224 from msgpack/gfx/decoder-and-encoder-take-object-params
let Encoder and Decoder accept named params as encode() and decode() do
2 parents c4d65d6 + 842dc45 commit 7ad7809

9 files changed

+242
-250
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ https://github.com/msgpack/msgpack-javascript/compare/v2.8.0...v3.0.0-beta1
77
* Add an option `useBigInt64` to map JavaScript's BigInt to MessagePack's int64 and uint64 ([#223](https://github.com/msgpack/msgpack-javascript/pull/223))
88
* Drop IE11 support ([#221](https://github.com/msgpack/msgpack-javascript/pull/221))
99
* It also fixes [feature request: option to disable TEXT_ENCODING env check #219](https://github.com/msgpack/msgpack-javascript/issues/219)
10-
*
10+
* Change the interfaces of `Encoder` and `Decoder`, and describe the interfaces in README.md ([#224](https://github.com/msgpack/msgpack-javascript/pull/224)):
11+
* `new Encoder(options: EncoderOptions)`: it takes the same named-options as `encode()`
12+
* `new Decoder(options: DecoderOptions)`: it takes the same named-options as `decode()`
1113

1214
## 2.8.0 2022-09-02
1315

README.md

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,14 @@ deepStrictEqual(decode(encoded), object);
3838
- [Table of Contents](#table-of-contents)
3939
- [Install](#install)
4040
- [API](#api)
41-
- [`encode(data: unknown, options?: EncodeOptions): Uint8Array`](#encodedata-unknown-options-encodeoptions-uint8array)
42-
- [`EncodeOptions`](#encodeoptions)
43-
- [`decode(buffer: ArrayLike<number> | BufferSource, options?: DecodeOptions): unknown`](#decodebuffer-arraylikenumber--buffersource-options-decodeoptions-unknown)
44-
- [`DecodeOptions`](#decodeoptions)
45-
- [`decodeMulti(buffer: ArrayLike<number> | BufferSource, options?: DecodeOptions): Generator<unknown, void, unknown>`](#decodemultibuffer-arraylikenumber--buffersource-options-decodeoptions-generatorunknown-void-unknown)
46-
- [`decodeAsync(stream: ReadableStreamLike<ArrayLike<number> | BufferSource>, options?: DecodeAsyncOptions): Promise<unknown>`](#decodeasyncstream-readablestreamlikearraylikenumber--buffersource-options-decodeasyncoptions-promiseunknown)
47-
- [`decodeArrayStream(stream: ReadableStreamLike<ArrayLike<number> | BufferSource>, options?: DecodeAsyncOptions): AsyncIterable<unknown>`](#decodearraystreamstream-readablestreamlikearraylikenumber--buffersource-options-decodeasyncoptions-asynciterableunknown)
48-
- [`decodeMultiStream(stream: ReadableStreamLike<ArrayLike<number> | BufferSource>, options?: DecodeAsyncOptions): AsyncIterable<unknown>`](#decodemultistreamstream-readablestreamlikearraylikenumber--buffersource-options-decodeasyncoptions-asynciterableunknown)
41+
- [`encode(data: unknown, options?: EncoderOptions): Uint8Array`](#encodedata-unknown-options-encoderoptions-uint8array)
42+
- [`EncoderOptions`](#encoderoptions)
43+
- [`decode(buffer: ArrayLike<number> | BufferSource, options?: DecoderOptions): unknown`](#decodebuffer-arraylikenumber--buffersource-options-decoderoptions-unknown)
44+
- [`DecoderOptions`](#decoderoptions)
45+
- [`decodeMulti(buffer: ArrayLike<number> | BufferSource, options?: DecoderOptions): Generator<unknown, void, unknown>`](#decodemultibuffer-arraylikenumber--buffersource-options-decoderoptions-generatorunknown-void-unknown)
46+
- [`decodeAsync(stream: ReadableStreamLike<ArrayLike<number> | BufferSource>, options?: DecoderOptions): Promise<unknown>`](#decodeasyncstream-readablestreamlikearraylikenumber--buffersource-options-decoderoptions-promiseunknown)
47+
- [`decodeArrayStream(stream: ReadableStreamLike<ArrayLike<number> | BufferSource>, options?: DecoderOptions): AsyncIterable<unknown>`](#decodearraystreamstream-readablestreamlikearraylikenumber--buffersource-options-decoderoptions-asynciterableunknown)
48+
- [`decodeMultiStream(stream: ReadableStreamLike<ArrayLike<number> | BufferSource>, options?: DecoderOptions): AsyncIterable<unknown>`](#decodemultistreamstream-readablestreamlikearraylikenumber--buffersource-options-decoderoptions-asynciterableunknown)
4949
- [Reusing Encoder and Decoder instances](#reusing-encoder-and-decoder-instances)
5050
- [Extension Types](#extension-types)
5151
- [ExtensionCodec context](#extensioncodec-context)
@@ -80,7 +80,7 @@ npm install @msgpack/msgpack
8080

8181
## API
8282

83-
### `encode(data: unknown, options?: EncodeOptions): Uint8Array`
83+
### `encode(data: unknown, options?: EncoderOptions): Uint8Array`
8484

8585
It encodes `data` into a single MessagePack-encoded object, and returns a byte array as `Uint8Array`. It throws errors if `data` is, or includes, a non-serializable object such as a `function` or a `symbol`.
8686

@@ -105,7 +105,7 @@ const buffer: Buffer = Buffer.from(encoded.buffer, encoded.byteOffset, encoded.b
105105
console.log(buffer);
106106
```
107107

108-
#### `EncodeOptions`
108+
#### `EncoderOptions`
109109

110110
Name|Type|Default
111111
----|----|----
@@ -118,7 +118,7 @@ forceIntegerToFloat | boolean | false
118118
ignoreUndefined | boolean | false
119119
context | user-defined | -
120120

121-
### `decode(buffer: ArrayLike<number> | BufferSource, options?: DecodeOptions): unknown`
121+
### `decode(buffer: ArrayLike<number> | BufferSource, options?: DecoderOptions): unknown`
122122

123123
It decodes `buffer` that includes a MessagePack-encoded object, and returns the decoded object typed `unknown`.
124124

@@ -138,7 +138,7 @@ console.log(object);
138138

139139
NodeJS `Buffer` is also acceptable because it is a subclass of `Uint8Array`.
140140

141-
#### `DecodeOptions`
141+
#### `DecoderOptions`
142142

143143
Name|Type|Default
144144
----|----|----
@@ -152,7 +152,7 @@ context | user-defined | -
152152

153153
You can use `max${Type}Length` to limit the length of each type decoded.
154154

155-
### `decodeMulti(buffer: ArrayLike<number> | BufferSource, options?: DecodeOptions): Generator<unknown, void, unknown>`
155+
### `decodeMulti(buffer: ArrayLike<number> | BufferSource, options?: DecoderOptions): Generator<unknown, void, unknown>`
156156

157157
It decodes `buffer` that includes multiple MessagePack-encoded objects, and returns decoded objects as a generator. See also `decodeMultiStream()`, which is an asynchronous variant of this function.
158158

@@ -170,14 +170,12 @@ for (const object of decodeMulti(encoded)) {
170170
}
171171
```
172172

173-
### `decodeAsync(stream: ReadableStreamLike<ArrayLike<number> | BufferSource>, options?: DecodeAsyncOptions): Promise<unknown>`
173+
### `decodeAsync(stream: ReadableStreamLike<ArrayLike<number> | BufferSource>, options?: DecoderOptions): Promise<unknown>`
174174

175175
It decodes `stream`, where `ReadableStreamLike<T>` is defined as `ReadableStream<T> | AsyncIterable<T>`, in an async iterable of byte arrays, and returns decoded object as `unknown` type, wrapped in `Promise`.
176176

177177
This function works asynchronously, and might CPU resources more efficiently compared with synchronous `decode()`, because it doesn't wait for the completion of downloading.
178178

179-
`DecodeAsyncOptions` is the same as `DecodeOptions` for `decode()`.
180-
181179
This function is designed to work with whatwg `fetch()` like this:
182180

183181
```typescript
@@ -193,7 +191,7 @@ if (contentType && contentType.startsWith(MSGPACK_TYPE) && response.body != null
193191
} else { /* handle errors */ }
194192
```
195193

196-
### `decodeArrayStream(stream: ReadableStreamLike<ArrayLike<number> | BufferSource>, options?: DecodeAsyncOptions): AsyncIterable<unknown>`
194+
### `decodeArrayStream(stream: ReadableStreamLike<ArrayLike<number> | BufferSource>, options?: DecoderOptions): AsyncIterable<unknown>`
197195

198196
It is alike to `decodeAsync()`, but only accepts a `stream` that includes an array of items, and emits a decoded item one by one.
199197

@@ -210,7 +208,7 @@ for await (const item of decodeArrayStream(stream)) {
210208
}
211209
```
212210

213-
### `decodeMultiStream(stream: ReadableStreamLike<ArrayLike<number> | BufferSource>, options?: DecodeAsyncOptions): AsyncIterable<unknown>`
211+
### `decodeMultiStream(stream: ReadableStreamLike<ArrayLike<number> | BufferSource>, options?: DecoderOptions): AsyncIterable<unknown>`
214212

215213
It is alike to `decodeAsync()` and `decodeArrayStream()`, but the input `stream` must consist of multiple MessagePack-encoded items. This is an asynchronous variant for `decodeMulti()`.
216214

@@ -233,7 +231,7 @@ This function is available since v2.4.0; previously it was called as `decodeStre
233231

234232
### Reusing Encoder and Decoder instances
235233

236-
`Encoder` and `Decoder` classes is provided to have better performance by reusing instances:
234+
`Encoder` and `Decoder` classes are provided to have better performance by reusing instances:
237235

238236
```typescript
239237
import { deepStrictEqual } from "assert";
@@ -251,6 +249,8 @@ than `encode()` function, and reusing `Decoder` instance is about 2% faster
251249
than `decode()` function. Note that the result should vary in environments
252250
and data structure.
253251

252+
`Encoder` and `Decoder` take the same options as `encode()` and `decode()` respectively.
253+
254254
## Extension Types
255255

256256
To handle [MessagePack Extension Types](https://github.com/msgpack/msgpack/blob/master/spec.md#extension-types), this library provides `ExtensionCodec` class.
@@ -304,7 +304,7 @@ Not that extension types for custom objects must be `[0, 127]`, while `[-1, -128
304304

305305
#### ExtensionCodec context
306306

307-
When you use an extension codec, it might be necessary to have encoding/decoding state to keep track of which objects got encoded/re-created. To do this, pass a `context` to the `EncodeOptions` and `DecodeOptions`:
307+
When you use an extension codec, it might be necessary to have encoding/decoding state to keep track of which objects got encoded/re-created. To do this, pass a `context` to the `EncoderOptions` and `DecoderOptions`:
308308

309309
```typescript
310310
import { encode, decode, ExtensionCodec } from "@msgpack/msgpack";

src/Decoder.ts

Lines changed: 77 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,61 @@ import { utf8Decode } from "./utils/utf8";
55
import { createDataView, ensureUint8Array } from "./utils/typedArrays";
66
import { CachedKeyDecoder, KeyDecoder } from "./CachedKeyDecoder";
77
import { DecodeError } from "./DecodeError";
8+
import type { ContextOf } from "./context";
9+
10+
export type DecoderOptions<ContextType = undefined> = Readonly<
11+
Partial<{
12+
extensionCodec: ExtensionCodecType<ContextType>;
13+
14+
/**
15+
* Decodes Int64 and Uint64 as bigint if it's set to true.
16+
* Depends on ES2020's {@link DataView#getBigInt64} and
17+
* {@link DataView#getBigUint64}.
18+
*
19+
* Defaults to false.
20+
*/
21+
useBigInt64: boolean;
22+
23+
/**
24+
* Maximum string length.
25+
*
26+
* Defaults to 4_294_967_295 (UINT32_MAX).
27+
*/
28+
maxStrLength: number;
29+
/**
30+
* Maximum binary length.
31+
*
32+
* Defaults to 4_294_967_295 (UINT32_MAX).
33+
*/
34+
maxBinLength: number;
35+
/**
36+
* Maximum array length.
37+
*
38+
* Defaults to 4_294_967_295 (UINT32_MAX).
39+
*/
40+
maxArrayLength: number;
41+
/**
42+
* Maximum map length.
43+
*
44+
* Defaults to 4_294_967_295 (UINT32_MAX).
45+
*/
46+
maxMapLength: number;
47+
/**
48+
* Maximum extension length.
49+
*
50+
* Defaults to 4_294_967_295 (UINT32_MAX).
51+
*/
52+
maxExtLength: number;
53+
54+
/**
55+
* An object key decoder. Defaults to the shared instance of {@link CachedKeyDecoder}.
56+
* `null` is a special value to disable the use of the key decoder at all.
57+
*/
58+
keyDecoder: KeyDecoder | null;
59+
}>
60+
> &
61+
ContextOf<ContextType>;
62+
863

964
const STATE_ARRAY = "array";
1065
const STATE_MAP_KEY = "map_key";
@@ -54,6 +109,16 @@ const MORE_DATA = new DataViewIndexOutOfBoundsError("Insufficient data");
54109
const sharedCachedKeyDecoder = new CachedKeyDecoder();
55110

56111
export class Decoder<ContextType = undefined> {
112+
private readonly extensionCodec: ExtensionCodecType<ContextType>;
113+
private readonly context: ContextType;
114+
private readonly useBigInt64: boolean;
115+
private readonly maxStrLength: number;
116+
private readonly maxBinLength: number;
117+
private readonly maxArrayLength: number;
118+
private readonly maxMapLength: number;
119+
private readonly maxExtLength: number;
120+
private readonly keyDecoder: KeyDecoder | null;
121+
57122
private totalPos = 0;
58123
private pos = 0;
59124

@@ -62,17 +127,18 @@ export class Decoder<ContextType = undefined> {
62127
private headByte = HEAD_BYTE_REQUIRED;
63128
private readonly stack: Array<StackState> = [];
64129

65-
public constructor(
66-
private readonly extensionCodec: ExtensionCodecType<ContextType> = ExtensionCodec.defaultCodec as any,
67-
private readonly context: ContextType = undefined as any,
68-
private readonly useBigInt64 = false,
69-
private readonly maxStrLength = UINT32_MAX,
70-
private readonly maxBinLength = UINT32_MAX,
71-
private readonly maxArrayLength = UINT32_MAX,
72-
private readonly maxMapLength = UINT32_MAX,
73-
private readonly maxExtLength = UINT32_MAX,
74-
private readonly keyDecoder: KeyDecoder | null = sharedCachedKeyDecoder,
75-
) {}
130+
public constructor(options?: DecoderOptions<ContextType>) {
131+
this.extensionCodec = options?.extensionCodec ?? (ExtensionCodec.defaultCodec as ExtensionCodecType<ContextType>);
132+
this.context = (options as { context: ContextType } | undefined)?.context as ContextType; // needs a type assertion because EncoderOptions has no context property when ContextType is undefined
133+
134+
this.useBigInt64 = options?.useBigInt64 ?? false;
135+
this.maxStrLength = options?.maxStrLength ?? UINT32_MAX;
136+
this.maxBinLength = options?.maxBinLength ?? UINT32_MAX;
137+
this.maxArrayLength = options?.maxArrayLength ?? UINT32_MAX;
138+
this.maxMapLength = options?.maxMapLength ?? UINT32_MAX;
139+
this.maxExtLength = options?.maxExtLength ?? UINT32_MAX;
140+
this.keyDecoder = (options?.keyDecoder !== undefined) ? options.keyDecoder : sharedCachedKeyDecoder;
141+
}
76142

77143
private reinitializeState() {
78144
this.totalPos = 0;

src/Encoder.ts

Lines changed: 94 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,105 @@ import { ExtensionCodec, ExtensionCodecType } from "./ExtensionCodec";
33
import { setInt64, setUint64 } from "./utils/int";
44
import { ensureUint8Array } from "./utils/typedArrays";
55
import type { ExtData } from "./ExtData";
6+
import type { ContextOf, SplitUndefined } from "./context";
7+
68

79
export const DEFAULT_MAX_DEPTH = 100;
810
export const DEFAULT_INITIAL_BUFFER_SIZE = 2048;
911

12+
export type EncoderOptions<ContextType = undefined> = Partial<
13+
Readonly<{
14+
extensionCodec: ExtensionCodecType<ContextType>;
15+
16+
/**
17+
* Encodes bigint as Int64 or Uint64 if it's set to true.
18+
* {@link forceIntegerToFloat} does not affect bigint.
19+
* Depends on ES2020's {@link DataView#setBigInt64} and
20+
* {@link DataView#setBigUint64}.
21+
*
22+
* Defaults to false.
23+
*/
24+
useBigInt64: boolean;
25+
26+
/**
27+
* The maximum depth in nested objects and arrays.
28+
*
29+
* Defaults to 100.
30+
*/
31+
maxDepth: number;
32+
33+
/**
34+
* The initial size of the internal buffer.
35+
*
36+
* Defaults to 2048.
37+
*/
38+
initialBufferSize: number;
39+
40+
/**
41+
* If `true`, the keys of an object is sorted. In other words, the encoded
42+
* binary is canonical and thus comparable to another encoded binary.
43+
*
44+
* Defaults to `false`. If enabled, it spends more time in encoding objects.
45+
*/
46+
sortKeys: boolean;
47+
/**
48+
* If `true`, non-integer numbers are encoded in float32, not in float64 (the default).
49+
*
50+
* Only use it if precisions don't matter.
51+
*
52+
* Defaults to `false`.
53+
*/
54+
forceFloat32: boolean;
55+
56+
/**
57+
* If `true`, an object property with `undefined` value are ignored.
58+
* e.g. `{ foo: undefined }` will be encoded as `{}`, as `JSON.stringify()` does.
59+
*
60+
* Defaults to `false`. If enabled, it spends more time in encoding objects.
61+
*/
62+
ignoreUndefined: boolean;
63+
64+
/**
65+
* If `true`, integer numbers are encoded as floating point numbers,
66+
* with the `forceFloat32` option taken into account.
67+
*
68+
* Defaults to `false`.
69+
*/
70+
forceIntegerToFloat: boolean;
71+
}>
72+
> & ContextOf<ContextType>;
73+
1074
export class Encoder<ContextType = undefined> {
11-
private pos = 0;
12-
private view = new DataView(new ArrayBuffer(this.initialBufferSize));
13-
private bytes = new Uint8Array(this.view.buffer);
14-
15-
public constructor(
16-
private readonly extensionCodec: ExtensionCodecType<ContextType> = ExtensionCodec.defaultCodec as any,
17-
private readonly context: ContextType = undefined as any,
18-
private readonly useBigInt64 = false,
19-
private readonly maxDepth = DEFAULT_MAX_DEPTH,
20-
private readonly initialBufferSize = DEFAULT_INITIAL_BUFFER_SIZE,
21-
private readonly sortKeys = false,
22-
private readonly forceFloat32 = false,
23-
private readonly ignoreUndefined = false,
24-
private readonly forceIntegerToFloat = false,
25-
) {}
75+
private readonly extensionCodec: ExtensionCodecType<ContextType>;
76+
private readonly context: ContextType;
77+
private readonly useBigInt64: boolean;
78+
private readonly maxDepth: number;
79+
private readonly initialBufferSize: number;
80+
private readonly sortKeys: boolean;
81+
private readonly forceFloat32: boolean;
82+
private readonly ignoreUndefined: boolean;
83+
private readonly forceIntegerToFloat: boolean;
84+
85+
private pos: number;
86+
private view: DataView;
87+
private bytes: Uint8Array;
88+
89+
public constructor(options?: EncoderOptions<ContextType>) {
90+
this.extensionCodec = options?.extensionCodec ?? (ExtensionCodec.defaultCodec as ExtensionCodecType<ContextType>);
91+
this.context = (options as { context: ContextType } | undefined)?.context as ContextType; // needs a type assertion because EncoderOptions has no context property when ContextType is undefined
92+
93+
this.useBigInt64 = options?.useBigInt64 ?? false;
94+
this.maxDepth = options?.maxDepth ?? DEFAULT_MAX_DEPTH;
95+
this.initialBufferSize = options?.initialBufferSize ?? DEFAULT_INITIAL_BUFFER_SIZE;
96+
this.sortKeys = options?.sortKeys ?? false;
97+
this.forceFloat32 = options?.forceFloat32 ?? false;
98+
this.ignoreUndefined = options?.ignoreUndefined ?? false;
99+
this.forceIntegerToFloat = options?.forceIntegerToFloat ?? false;
100+
101+
this.pos = 0;
102+
this.view = new DataView(new ArrayBuffer(this.initialBufferSize));
103+
this.bytes = new Uint8Array(this.view.buffer);
104+
}
26105

27106
private reinitializeState() {
28107
this.pos = 0;

0 commit comments

Comments
 (0)