"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ReflectionJsonWriter = void 0; const base64_1 = require("./base64"); const pb_long_1 = require("./pb-long"); const reflection_info_1 = require("./reflection-info"); const assert_1 = require("./assert"); /** * Writes proto3 messages in canonical JSON format using reflection * information. * * https://developers.google.com/protocol-buffers/docs/proto3#json */ class ReflectionJsonWriter { constructor(info) { var _a; this.fields = (_a = info.fields) !== null && _a !== void 0 ? _a : []; } /** * Converts the message to a JSON object, based on the field descriptors. */ write(message, options) { const json = {}, source = message; for (const field of this.fields) { // field is not part of a oneof, simply write as is if (!field.oneof) { let jsonValue = this.field(field, source[field.localName], options); if (jsonValue !== undefined) json[options.useProtoFieldName ? field.name : field.jsonName] = jsonValue; continue; } // field is part of a oneof const group = source[field.oneof]; if (group.oneofKind !== field.localName) continue; // not selected, skip const opt = field.kind == 'scalar' || field.kind == 'enum' ? Object.assign(Object.assign({}, options), { emitDefaultValues: true }) : options; let jsonValue = this.field(field, group[field.localName], opt); assert_1.assert(jsonValue !== undefined); json[options.useProtoFieldName ? field.name : field.jsonName] = jsonValue; } return json; } field(field, value, options) { let jsonValue = undefined; if (field.kind == 'map') { assert_1.assert(typeof value == "object" && value !== null); const jsonObj = {}; switch (field.V.kind) { case "scalar": for (const [entryKey, entryValue] of Object.entries(value)) { const val = this.scalar(field.V.T, entryValue, field.name, false, true); assert_1.assert(val !== undefined); jsonObj[entryKey.toString()] = val; // JSON standard allows only (double quoted) string as property key } break; case "message": const messageType = field.V.T(); for (const [entryKey, entryValue] of Object.entries(value)) { const val = this.message(messageType, entryValue, field.name, options); assert_1.assert(val !== undefined); jsonObj[entryKey.toString()] = val; // JSON standard allows only (double quoted) string as property key } break; case "enum": const enumInfo = field.V.T(); for (const [entryKey, entryValue] of Object.entries(value)) { assert_1.assert(entryValue === undefined || typeof entryValue == 'number'); const val = this.enum(enumInfo, entryValue, field.name, false, true, options.enumAsInteger); assert_1.assert(val !== undefined); jsonObj[entryKey.toString()] = val; // JSON standard allows only (double quoted) string as property key } break; } if (options.emitDefaultValues || Object.keys(jsonObj).length > 0) jsonValue = jsonObj; } else if (field.repeat) { assert_1.assert(Array.isArray(value)); const jsonArr = []; switch (field.kind) { case "scalar": for (let i = 0; i < value.length; i++) { const val = this.scalar(field.T, value[i], field.name, field.opt, true); assert_1.assert(val !== undefined); jsonArr.push(val); } break; case "enum": const enumInfo = field.T(); for (let i = 0; i < value.length; i++) { assert_1.assert(value[i] === undefined || typeof value[i] == 'number'); const val = this.enum(enumInfo, value[i], field.name, field.opt, true, options.enumAsInteger); assert_1.assert(val !== undefined); jsonArr.push(val); } break; case "message": const messageType = field.T(); for (let i = 0; i < value.length; i++) { const val = this.message(messageType, value[i], field.name, options); assert_1.assert(val !== undefined); jsonArr.push(val); } break; } // add converted array to json output if (options.emitDefaultValues || jsonArr.length > 0 || options.emitDefaultValues) jsonValue = jsonArr; } else { switch (field.kind) { case "scalar": jsonValue = this.scalar(field.T, value, field.name, field.opt, options.emitDefaultValues); break; case "enum": jsonValue = this.enum(field.T(), value, field.name, field.opt, options.emitDefaultValues, options.enumAsInteger); break; case "message": jsonValue = this.message(field.T(), value, field.name, options); break; } } return jsonValue; } /** * Returns `null` as the default for google.protobuf.NullValue. */ enum(type, value, fieldName, optional, emitDefaultValues, enumAsInteger) { if (type[0] == 'google.protobuf.NullValue') return !emitDefaultValues && !optional ? undefined : null; if (value === undefined) { assert_1.assert(optional); return undefined; } if (value === 0 && !emitDefaultValues && !optional) // we require 0 to be default value for all enums return undefined; assert_1.assert(typeof value == 'number'); assert_1.assert(Number.isInteger(value)); if (enumAsInteger || !type[1].hasOwnProperty(value)) // if we don't now the enum value, just return the number return value; if (type[2]) // restore the dropped prefix return type[2] + type[1][value]; return type[1][value]; } message(type, value, fieldName, options) { if (value === undefined) return options.emitDefaultValues ? null : undefined; return type.internalJsonWrite(value, options); } scalar(type, value, fieldName, optional, emitDefaultValues) { if (value === undefined) { assert_1.assert(optional); return undefined; } const ed = emitDefaultValues || optional; // noinspection FallThroughInSwitchStatementJS switch (type) { // int32, fixed32, uint32: JSON value will be a decimal number. Either numbers or strings are accepted. case reflection_info_1.ScalarType.INT32: case reflection_info_1.ScalarType.SFIXED32: case reflection_info_1.ScalarType.SINT32: if (value === 0) return ed ? 0 : undefined; assert_1.assertInt32(value); return value; case reflection_info_1.ScalarType.FIXED32: case reflection_info_1.ScalarType.UINT32: if (value === 0) return ed ? 0 : undefined; assert_1.assertUInt32(value); return value; // float, double: JSON value will be a number or one of the special string values "NaN", "Infinity", and "-Infinity". // Either numbers or strings are accepted. Exponent notation is also accepted. case reflection_info_1.ScalarType.FLOAT: assert_1.assertFloat32(value); case reflection_info_1.ScalarType.DOUBLE: if (value === 0) return ed ? 0 : undefined; assert_1.assert(typeof value == 'number'); if (Number.isNaN(value)) return 'NaN'; if (value === Number.POSITIVE_INFINITY) return 'Infinity'; if (value === Number.NEGATIVE_INFINITY) return '-Infinity'; return value; // string: case reflection_info_1.ScalarType.STRING: if (value === "") return ed ? '' : undefined; assert_1.assert(typeof value == 'string'); return value; // bool: case reflection_info_1.ScalarType.BOOL: if (value === false) return ed ? false : undefined; assert_1.assert(typeof value == 'boolean'); return value; // JSON value will be a decimal string. Either numbers or strings are accepted. case reflection_info_1.ScalarType.UINT64: case reflection_info_1.ScalarType.FIXED64: assert_1.assert(typeof value == 'number' || typeof value == 'string' || typeof value == 'bigint'); let ulong = pb_long_1.PbULong.from(value); if (ulong.isZero() && !ed) return undefined; return ulong.toString(); // JSON value will be a decimal string. Either numbers or strings are accepted. case reflection_info_1.ScalarType.INT64: case reflection_info_1.ScalarType.SFIXED64: case reflection_info_1.ScalarType.SINT64: assert_1.assert(typeof value == 'number' || typeof value == 'string' || typeof value == 'bigint'); let long = pb_long_1.PbLong.from(value); if (long.isZero() && !ed) return undefined; return long.toString(); // bytes: JSON value will be the data encoded as a string using standard base64 encoding with paddings. // Either standard or URL-safe base64 encoding with/without paddings are accepted. case reflection_info_1.ScalarType.BYTES: assert_1.assert(value instanceof Uint8Array); if (!value.byteLength) return ed ? "" : undefined; return base64_1.base64encode(value); } } } exports.ReflectionJsonWriter = ReflectionJsonWriter;