-
Notifications
You must be signed in to change notification settings - Fork 124
/
Copy pathSerializer.js
179 lines (166 loc) · 6.02 KB
/
Serializer.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
'use strict';
const { stringify } = require('querystring');
const debug = require('debug')('opensearch');
const sjson = require('secure-json-parse');
const { SerializationError, DeserializationError } = require('./errors');
const kJsonOptions = Symbol('secure json parse options');
const JSON11 = require('json11');
const isBigIntSupported = typeof BigInt !== 'undefined';
class Serializer {
constructor(opts = {}) {
const disable = opts.disablePrototypePoisoningProtection;
this[kJsonOptions] = {
protoAction: disable === true || disable === 'proto' ? 'ignore' : 'error',
constructorAction: disable === true || disable === 'constructor' ? 'ignore' : 'error',
enableLongNumeralSupport: opts.enableLongNumeralSupport === true,
};
}
serialize(object) {
debug('Serializing', object);
let json;
let numeralsAreNumbers = true;
const checkForBigInts = (key, val) => {
if (typeof val === 'bigint') {
numeralsAreNumbers = false;
// Number() is much faster than parseInt() on BigInt values
return Number(val);
}
return val;
};
const shouldHandleLongNumerals =
isBigIntSupported && this[kJsonOptions].enableLongNumeralSupport;
try {
/* When handling long numerals is not requested or the platform doesn't support BigInt, the
* result of JSON.stringify are returned.
*
* When long numerals should be handled:
* Use JSON.stringify to check if any value is a BigInt:
* * If no BigInt values are found, the result of JSON.stringify is good enough to be returned.
* * Only If a BigInt value is found, JSON11.stringify is employed and its result is returned.
*/
json = JSON.stringify(object, shouldHandleLongNumerals ? checkForBigInts : null);
if (shouldHandleLongNumerals && !numeralsAreNumbers) {
try {
// With `withBigInt: false`, valid JSON is produced while maintaining accuracy
const temp = JSON11.stringify(object, {
withBigInt: false,
quote: '"',
quoteNames: true,
});
if (temp) json = temp;
} catch (ex) {
// Do nothing: JSON.stringify succeeded but JSON11.stringify failed; return the
// JSON.stringify result.
}
}
} catch (err) {
throw new SerializationError(err.message, object);
}
return json;
}
deserialize(json) {
debug('Deserializing', json);
let object;
let numeralsAreNumbers = true;
const checkForLargeNumerals = (key, val) => {
if (
numeralsAreNumbers &&
typeof val === 'number' &&
(val < Number.MIN_SAFE_INTEGER || val > Number.MAX_SAFE_INTEGER)
) {
numeralsAreNumbers = false;
}
return val;
};
const shouldHandleLongNumerals =
isBigIntSupported && this[kJsonOptions].enableLongNumeralSupport;
try {
/* When handling long numerals is not requested or the platform doesn't support BigInt, the
* result of sjson.parse are returned.
*
* When long numerals should be handled:
* Use sjson.parse to check if any value is outside the range of safe integers:
* * If no long numerals are found, the result of sjson.parse is good enough to be returned.
* * Only If long numerals are found, JSON11.parse is employed and its result is returned.
*/
object = sjson.parse(
json,
shouldHandleLongNumerals ? checkForLargeNumerals : null,
this[kJsonOptions]
);
if (shouldHandleLongNumerals && !numeralsAreNumbers) {
try {
const temp = JSON11.parse(json, null, { withLongNumerals: true });
if (temp) {
object = temp;
}
} catch (ex) {
// Do nothing: sjson.parse succeeded but JSON11.parse failed; return the sjson.parse result
}
}
} catch (err) {
throw new DeserializationError(err.message, json);
}
return object;
}
ndserialize(array) {
debug('ndserialize', array);
if (Array.isArray(array) === false) {
throw new SerializationError('The argument provided is not an array', array);
}
let ndjson = '';
for (let i = 0, len = array.length; i < len; i++) {
if (typeof array[i] === 'string') {
ndjson += array[i] + '\n';
} else {
ndjson += this.serialize(array[i]) + '\n';
}
}
return ndjson;
}
qserialize(object) {
debug('qserialize', object);
if (object == null) return '';
if (typeof object === 'string') return object;
// arrays should be serialized as comma separated list
const keys = Object.keys(object);
for (let i = 0, len = keys.length; i < len; i++) {
const key = keys[i];
// OpenSearch will complain about keys without a value
if (object[key] === undefined) {
delete object[key];
} else if (Array.isArray(object[key]) === true) {
object[key] = object[key].join(',');
}
}
return stringify(object);
}
}
module.exports = Serializer;