/
convert.js
168 lines (151 loc) · 6.26 KB
/
convert.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
"use strict";
module.exports = convert;
var Enum = require("./enum"),
util = require("./util");
var Type, // cyclic
Message;
/**
* A converter as used by {@link convert}.
* @typedef Converter
* @type {function}
* @param {Field} field Reflected field
* @param {*} value Value to convert
* @param {Object.<string,*>} options Conversion options
* @returns {*} Converted value
*/
/**
* Converts between JSON objects and messages, based on reflection information.
* @param {Type} type Type
* @param {*} source Source object
* @param {*} destination Destination object
* @param {Object.<string,*>} options Conversion options
* @param {Converter} converter Conversion function
* @returns {*} `destination`
* @property {Converter} toJson To JSON converter using {@link JSONConversionOptions}
* @property {Converter} toMessage To message converter using {@link MessageConversionOptions}
*/
function convert(type, source, destination, options, converter) {
if (!Type) { // require this here already so it is available within the converters below
Type = require("./type");
Message = require("./message");
}
if (!options)
options = {};
var keys = Object.keys(options.defaults ? type.fields : source);
for (var i = 0, key; i < keys.length; ++i) {
var field = type.fields[key = keys[i]],
value = source[key];
if (field) {
if (field.repeated) {
if (value || options.defaults || options.arrays) {
destination[key] = [];
if (value)
for (var j = 0, l = value.length; j < l; ++j)
destination[key].push(converter(field, value[j], options));
}
} else
destination[key] = converter(field, value, options);
} else if (!options.fieldsOnly)
destination[key] = value;
}
return destination;
}
/**
* JSON conversion options as used by {@link Message#asJSON} with {@link convert}.
* @typedef JSONConversionOptions
* @type {Object}
* @property {boolean} [fieldsOnly=false] If `true`, keeps only properties that reference a field.
* @property {*} [longs] Long conversion type. Only relevant with a long library.
* Valid values are `String` and `Number` (the global types).
* Defaults to a possibly unsafe number without, and a `Long` with a long library.
* @property {*} [enums=Number] Enum value conversion type.
* Valid values are `String` and `Number` (the global types).
* Defaults to `Number`, which sets the numeric ids.
* @property {*} [bytes] Bytes value conversion type.
* Valid values are `Array` and `String` (the global types).
* Defaults to return the underlying buffer type, which usually is an `Uint8Array` in the browser and a `Buffer` under node.
* @property {boolean} [defaults=false] If `true`, sets default values on the resulting object including empty arrays for repeated fields
* @property {boolean} [arrays=false] If `true`, always initializes empty arrays for repeated fields. Only relevant with `defaults=false`.
*/
/**/
convert.toJson = function toJson(field, value, options) {
if (!options)
options = {};
// Recurse into inner messages
if (value instanceof Message)
return convert(value.$type, value, {}, options, toJson);
// Enums as strings
if (options.enums && field.resolvedType instanceof Enum)
return options.enums === String
? field.resolvedType.getValuesById()[value]
: value | 0;
// Longs as numbers or strings
if (options.longs && field.long) {
var unsigned = field.type.charAt(0) === "u";
if (options.longs === Number)
return typeof value === "number"
? value
: util.LongBits.from(value).toNumber(unsigned);
if (options.longs === String) {
if(typeof value === "number")
return util.Long.fromNumber(value, unsigned).toString();
value = util.Long.fromValue(value); // has no unsigned option
value.unsigned = unsigned;
return value.toString();
}
}
// Bytes as base64 strings, plain arrays or buffers
if (options.bytes && field.bytes) {
if (options.bytes === String)
return util.base64.encode(value, 0, value.length);
if (options.bytes === Array)
return Array.prototype.slice.call(value);
if (options.bytes === util.Buffer && !util.Buffer.isBuffer(value))
return util.Buffer.from(value); // polyfilled
}
return value;
};
/**
* Message conversion options as used by {@link Message.from} and {@link Type#from} with {@link convert}.
* @typedef MessageConversionOptions
* @type {Object}
* @property {boolean} [fieldsOnly=false] Keeps only properties that reference a field
*/
/**/
convert.toMessage = function toMessage(field, value, options) {
switch (typeof value) {
// Recurse into inner messages
case "object":
if (value) {
if (field.resolvedType instanceof Type)
return convert(field.resolvedType, value, new (field.resolvedType.getCtor())(), options, toMessage);
if (field.type === "bytes")
return util.Buffer
? util.Buffer.isBuffer(value)
? value
: util.Buffer.from(value) // polyfilled
: value instanceof util.Array
? value
: new util.Array(value);
}
break;
// Strings to proper numbers, longs or buffers
case "string":
if (field.resolvedType instanceof Enum)
return field.resolvedType.values[value] || 0;
if (field.long)
return util.Long.fromString(value, field.type.charAt(0) === "u");
if (field.bytes) {
var buf = util.newBuffer(util.base64.length(value));
util.base64.decode(value, buf, 0);
return buf;
}
break;
// Numbers to proper longs
case "number":
if (field.long)
return util.Long.fromNumber(value, field.type.charAt(0) === "u");
break;
}
return value;
};