Skip to content

Commit 7ef27c6

Browse files
authored
Merge pull request #590 from tiagosiebler/customParser1
feat(v3.1.0, #588): introduce custom JSON parser hook & small refinements
2 parents 0531914 + 6b4c08a commit 7ef27c6

File tree

9 files changed

+117
-25
lines changed

9 files changed

+117
-25
lines changed

README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,51 @@ wsClient.subscribeUsdFuturesUserDataStream();
428428
429429
See [websocket-client.ts](./src/websocket-client.ts) for further information. Also see [ws-userdata.ts](./examples/ws-userdata.ts) for user data examples.
430430
431+
#### Preserving large integers in WebSocket messages
432+
433+
By default, messages are parsed using `JSON.parse`, which cannot precisely represent integers larger than `Number.MAX_SAFE_INTEGER`.
434+
If you need to preserve large integers (e.g., order IDs), provide a custom parser via `customParseJSONFn`.
435+
436+
Example using RegEx below, although alternatives are possible too if desired. For more exampes check [ws-custom-parser.ts](./examples/WebSockets/ws-custom-parser.ts) in the examples folder:
437+
438+
```ts
439+
import { WebsocketClient } from 'binance';
440+
441+
/**
442+
* ETHUSDT in futures can have unusually large orderId values, sent as numbers. See this thread for more details:
443+
* https://github.com/tiagosiebler/binance/issues/208
444+
*
445+
* If this is a problem for you, you can set a custom JSON parsing alternative using the customParseJSONFn hook injected into the WebsocketClient's constructor, as below:
446+
*/
447+
const ws = new WebsocketClient({
448+
// Default behaviour, if you don't include this:
449+
// customParseJSONFn: (rawEvent) => {
450+
// return JSON.parse(rawEvent);
451+
// },
452+
453+
// Or, pre-process the raw event using RegEx, before using the same workflow:
454+
customParseJSONFn: (rawEvent) => {
455+
return JSON.parse(
456+
rawEvent.replace(/"orderId":\s*(\d+)/g, '"orderId":"$1"'),
457+
);
458+
},
459+
460+
// Or, use a 3rd party library such as json-bigint:
461+
// customParseJSONFn: (rawEvent) => {
462+
// return JSONbig({ storeAsString: true }).parse(rawEvent);
463+
// },
464+
});
465+
466+
ws.on('message', (msg) => {
467+
console.log(msg);
468+
});
469+
470+
// If you prefer native BigInt, beware JSON.stringify will throw on BigInt values.
471+
// Use a custom replacer or JSONbig.stringify if you need to log/serialize:
472+
// const replacer = (_k: string, v: unknown) => typeof v === 'bigint' ? v.toString() : v;
473+
// console.log(JSON.stringify(msg, replacer));
474+
```
475+
431476
### WebSocket API
432477
433478
Some of the product groups available on Binance also support sending requests (commands) over an active WebSocket connection. This is called the WebSocket API.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Optional, for 3rd scenario below:
2+
// Import 3rd party library for parsing big number types in JSON
3+
// import JSONbig from 'json-bigint';
4+
5+
import { WebsocketClient } from '../../src';
6+
7+
// Demonstrates using a custom JSON parser for incoming WS messages to preserve big integers
8+
9+
/**
10+
* ETHUSDT in futures can have unusually large orderId values, sent as numbers. See this thread for more details:
11+
* https://github.com/tiagosiebler/binance/issues/208
12+
*
13+
* If this is a problem for you, you can set a custom JSON parsing alternative using the customParseJSONFn hook injected into the WebsocketClient's constructor, as below:
14+
*/
15+
const ws = new WebsocketClient({
16+
// Default behaviour, if you don't include this:
17+
// customParseJSONFn: (rawEvent) => {
18+
// return JSON.parse(rawEvent);
19+
// },
20+
// Or, pre-process the raw event using RegEx, before using the same workflow:
21+
customParseJSONFn: (rawEvent) => {
22+
return JSON.parse(
23+
rawEvent.replace(/"orderId":\s*(\d+)/g, '"orderId":"$1"'),
24+
);
25+
},
26+
// Or, use a 3rd party library such as json-bigint:
27+
// customParseJSONFn: (rawEvent) => {
28+
// return JSONbig({ storeAsString: true }).parse(rawEvent);
29+
// },
30+
});
31+
32+
ws.on('open', ({ wsKey }) => {
33+
console.log('ws connected', wsKey);
34+
});
35+
36+
ws.on('message', (msg) => {
37+
console.log('msg:', msg);
38+
});
39+
40+
// Subscribe to a couple of topics
41+
ws.subscribe(['btcusdt@trade', '!ticker@arr'], 'main');

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "binance",
3-
"version": "3.0.9",
3+
"version": "3.1.0",
44
"description": "Professional Node.js & JavaScript SDK for Binance REST APIs & WebSockets, with TypeScript & end-to-end tests.",
55
"main": "lib/index.js",
66
"types": "lib/index.d.ts",

src/types/websockets/ws-general.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,12 @@ export interface WSClientConfigurableOptions {
178178
* Look in the examples folder for a demonstration on using node's createHmac instead.
179179
*/
180180
customSignMessageFn?: (message: string, secret: string) => Promise<string>;
181+
182+
/**
183+
* Optional custom JSON parser used for incoming WS messages.
184+
* Defaults to JSON.parse.
185+
*/
186+
customParseJSONFn?: (raw: string) => object;
181187
}
182188

183189
/**

src/util/websockets/websocket-util.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import WebSocket from 'isomorphic-ws';
44
import { WsRequestOperationBinance } from '../../types/websockets/ws-api';
55
import {
66
WebsocketClientOptions,
7+
WSClientConfigurableOptions,
78
WsMarket,
89
WsTopic,
910
} from '../../types/websockets/ws-general';
@@ -623,14 +624,20 @@ export function resolveWsKeyForLegacyMarket(
623624
}
624625
}
625626
}
626-
export function parseRawWsMessageLegacy(event: any) {
627+
export function parseRawWsMessageLegacy(
628+
event: any,
629+
options: WSClientConfigurableOptions,
630+
) {
627631
if (typeof event === 'string') {
628-
const parsedEvent = JSON.parse(event);
632+
const parsedEvent =
633+
typeof options.customParseJSONFn === 'function'
634+
? options.customParseJSONFn(event)
635+
: JSON.parse(event);
629636

630637
// WS events are wrapped into "data"
631638
if (parsedEvent.data) {
632639
if (typeof parsedEvent.data === 'string') {
633-
return parseRawWsMessageLegacy(parsedEvent.data);
640+
return parseRawWsMessageLegacy(parsedEvent.data, options);
634641
}
635642

636643
return parsedEvent.data;
@@ -645,7 +652,7 @@ export function parseRawWsMessageLegacy(event: any) {
645652
return parsedEvent;
646653
}
647654
if (event?.data) {
648-
return parseRawWsMessageLegacy(event.data);
655+
return parseRawWsMessageLegacy(event.data, options);
649656
}
650657
return event;
651658
}
@@ -655,10 +662,13 @@ export function parseRawWsMessageLegacy(event: any) {
655662
*
656663
* Any mapping or additonal handling should not be done here.
657664
*/
658-
export function parseRawWsMessage(event: any): any {
665+
export function parseRawWsMessage(
666+
event: any,
667+
options: WSClientConfigurableOptions,
668+
): any {
659669
// WS MessageLike->data (contains JSON as a string)
660670
if (event?.data) {
661-
return parseRawWsMessage(event.data);
671+
return parseRawWsMessage(event.data, options);
662672
}
663673

664674
if (typeof event === 'string') {
@@ -668,8 +678,11 @@ export function parseRawWsMessage(event: any): any {
668678
// - user data, via ws api (Without listen key)
669679
// - ws api responses
670680

671-
const parsedEvent = JSON.parse(event);
681+
if (typeof options.customParseJSONFn === 'function') {
682+
return options.customParseJSONFn(event);
683+
}
672684

685+
const parsedEvent = JSON.parse(event);
673686
return parsedEvent;
674687
}
675688

src/websocket-client-legacy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ export class WebsocketClientV1 extends EventEmitter {
331331
try {
332332
this.clearPongTimer(wsKey);
333333

334-
const msg = parseRawWsMessageLegacy(event);
334+
const msg = parseRawWsMessageLegacy(event, this.options);
335335

336336
// Edge case where raw event does not include event type, detect using wsKey and mutate msg.e
337337
const eventType = parseEventTypeFromMessage(wsKey as any, msg);

src/websocket-client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -780,7 +780,7 @@ export class WebsocketClient extends BaseWebsocketClient<
780780
* Extract event from JSON
781781
*
782782
*/
783-
const parsedEvent = parseRawWsMessage(event);
783+
const parsedEvent = parseRawWsMessage(event, this.options);
784784

785785
/**
786786
*

test/websocket-client.test.ts

Lines changed: 0 additions & 13 deletions
This file was deleted.

0 commit comments

Comments
 (0)