"use strict"; // Copyright 2021-2024 Buf Technologies, Inc. // // Licensed 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. Object.defineProperty(exports, "__esModule", { value: true }); exports.fromJsonString = fromJsonString; exports.mergeFromJsonString = mergeFromJsonString; exports.fromJson = fromJson; exports.mergeFromJson = mergeFromJson; exports.enumFromJson = enumFromJson; exports.isEnumJson = isEnumJson; /* eslint-disable no-case-declarations,@typescript-eslint/restrict-template-expressions */ const descriptors_js_1 = require("./descriptors.js"); const proto_int64_js_1 = require("./proto-int64.js"); const create_js_1 = require("./create.js"); const reflect_js_1 = require("./reflect/reflect.js"); const error_js_1 = require("./reflect/error.js"); const reflect_check_js_1 = require("./reflect/reflect-check.js"); const scalar_js_1 = require("./reflect/scalar.js"); const base64_encoding_js_1 = require("./wire/base64-encoding.js"); const index_js_1 = require("./wkt/index.js"); const extensions_js_1 = require("./extensions.js"); // Default options for parsing JSON. const jsonReadDefaults = { ignoreUnknownFields: false, }; function makeReadOptions(options) { return options ? Object.assign(Object.assign({}, jsonReadDefaults), options) : jsonReadDefaults; } /** * Parse a message from a JSON string. */ function fromJsonString(schema, json, options) { return fromJson(schema, parseJsonString(json, schema.typeName), options); } /** * Parse a message from a JSON string, merging fields. * * Repeated fields are appended. Map entries are added, overwriting * existing keys. * * If a message field is already present, it will be merged with the * new data. */ function mergeFromJsonString(schema, target, json, options) { return mergeFromJson(schema, target, parseJsonString(json, schema.typeName), options); } /** * Parse a message from a JSON value. */ function fromJson(schema, json, options) { const msg = (0, reflect_js_1.reflect)(schema); try { readMessage(msg, json, makeReadOptions(options)); } catch (e) { if ((0, error_js_1.isFieldError)(e)) { // @ts-expect-error we use the ES2022 error CTOR option "cause" for better stack traces throw new Error(`cannot decode ${e.field()} from JSON: ${e.message}`, { cause: e, }); } throw e; } return msg.message; } /** * Parse a message from a JSON value, merging fields. * * Repeated fields are appended. Map entries are added, overwriting * existing keys. * * If a message field is already present, it will be merged with the * new data. */ function mergeFromJson(schema, target, json, options) { try { readMessage((0, reflect_js_1.reflect)(schema, target), json, makeReadOptions(options)); } catch (e) { if ((0, error_js_1.isFieldError)(e)) { // @ts-expect-error we use the ES2022 error CTOR option "cause" for better stack traces throw new Error(`cannot decode ${e.field()} from JSON: ${e.message}`, { cause: e, }); } throw e; } return target; } /** * Parses an enum value from JSON. */ function enumFromJson(descEnum, json) { const val = readEnum(descEnum, json, false, false); if (val === tokenIgnoredUnknownEnum) { throw new Error(`cannot decode ${String(descEnum)} from JSON: ${(0, reflect_check_js_1.formatVal)(json)}`); } return val; } /** * Is the given value a JSON enum value? */ function isEnumJson(descEnum, value) { return undefined !== descEnum.values.find((v) => v.name === value); } function readMessage(msg, json, opts) { var _a; if (tryWktFromJson(msg, json, opts)) { return; } if (json == null || Array.isArray(json) || typeof json != "object") { throw new Error(`cannot decode ${msg.desc} from JSON: ${(0, reflect_check_js_1.formatVal)(json)}`); } const oneofSeen = new Map(); const jsonNames = new Map(); for (const field of msg.desc.fields) { jsonNames.set(field.name, field).set(field.jsonName, field); } for (const [jsonKey, jsonValue] of Object.entries(json)) { const field = jsonNames.get(jsonKey); if (field) { if (field.oneof) { if (jsonValue === null && field.fieldKind == "scalar") { // see conformance test Required.Proto3.JsonInput.OneofFieldNull{First,Second} continue; } const seen = oneofSeen.get(field.oneof); if (seen !== undefined) { throw new error_js_1.FieldError(field.oneof, `oneof set multiple times by ${seen.name} and ${field.name}`); } oneofSeen.set(field.oneof, field); } readField(msg, field, jsonValue, opts); } else { let extension = undefined; if (jsonKey.startsWith("[") && jsonKey.endsWith("]") && (extension = (_a = opts.registry) === null || _a === void 0 ? void 0 : _a.getExtension(jsonKey.substring(1, jsonKey.length - 1))) && extension.extendee.typeName === msg.desc.typeName) { const [container, field, get] = (0, extensions_js_1.createExtensionContainer)(extension); readField(container, field, jsonValue, opts); (0, extensions_js_1.setExtension)(msg.message, extension, get()); } if (!extension && !opts.ignoreUnknownFields) { throw new Error(`cannot decode ${msg.desc} from JSON: key "${jsonKey}" is unknown`); } } } } function readField(msg, field, json, opts) { switch (field.fieldKind) { case "scalar": readScalarField(msg, field, json); break; case "enum": readEnumField(msg, field, json, opts); break; case "message": readMessageField(msg, field, json, opts); break; case "list": readListField(msg.get(field), json, opts); break; case "map": readMapField(msg.get(field), json, opts); break; } } function readMapField(map, json, opts) { if (json === null) { return; } const field = map.field(); if (typeof json != "object" || Array.isArray(json)) { throw new error_js_1.FieldError(field, "expected object, got " + (0, reflect_check_js_1.formatVal)(json)); } for (const [jsonMapKey, jsonMapValue] of Object.entries(json)) { if (jsonMapValue === null) { throw new error_js_1.FieldError(field, "map value must not be null"); } let value; switch (field.mapKind) { case "message": const msgValue = (0, reflect_js_1.reflect)(field.message); readMessage(msgValue, jsonMapValue, opts); value = msgValue; break; case "enum": value = readEnum(field.enum, jsonMapValue, opts.ignoreUnknownFields, true); if (value === tokenIgnoredUnknownEnum) { return; } break; case "scalar": value = scalarFromJson(field, jsonMapValue, true); break; } const key = mapKeyFromJson(field.mapKey, jsonMapKey); map.set(key, value); } } function readListField(list, json, opts) { if (json === null) { return; } const field = list.field(); if (!Array.isArray(json)) { throw new error_js_1.FieldError(field, "expected Array, got " + (0, reflect_check_js_1.formatVal)(json)); } for (const jsonItem of json) { if (jsonItem === null) { throw new error_js_1.FieldError(field, "list item must not be null"); } switch (field.listKind) { case "message": const msgValue = (0, reflect_js_1.reflect)(field.message); readMessage(msgValue, jsonItem, opts); list.add(msgValue); break; case "enum": const enumValue = readEnum(field.enum, jsonItem, opts.ignoreUnknownFields, true); if (enumValue !== tokenIgnoredUnknownEnum) { list.add(enumValue); } break; case "scalar": list.add(scalarFromJson(field, jsonItem, true)); break; } } } function readMessageField(msg, field, json, opts) { if (json === null && field.message.typeName != "google.protobuf.Value") { msg.clear(field); return; } const msgValue = msg.isSet(field) ? msg.get(field) : (0, reflect_js_1.reflect)(field.message); readMessage(msgValue, json, opts); msg.set(field, msgValue); } function readEnumField(msg, field, json, opts) { const enumValue = readEnum(field.enum, json, opts.ignoreUnknownFields, false); if (enumValue === tokenNull) { msg.clear(field); } else if (enumValue !== tokenIgnoredUnknownEnum) { msg.set(field, enumValue); } } function readScalarField(msg, field, json) { const scalarValue = scalarFromJson(field, json, false); if (scalarValue === tokenNull) { msg.clear(field); } else { msg.set(field, scalarValue); } } const tokenIgnoredUnknownEnum = Symbol(); function readEnum(desc, json, ignoreUnknownFields, nullAsZeroValue) { if (json === null) { if (desc.typeName == "google.protobuf.NullValue") { return 0; // google.protobuf.NullValue.NULL_VALUE = 0 } return nullAsZeroValue ? desc.values[0].number : tokenNull; } // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check switch (typeof json) { case "number": if (Number.isInteger(json)) { return json; } break; case "string": const value = desc.values.find((ev) => ev.name === json); if (value !== undefined) { return value.number; } if (ignoreUnknownFields) { return tokenIgnoredUnknownEnum; } break; } throw new Error(`cannot decode ${desc} from JSON: ${(0, reflect_check_js_1.formatVal)(json)}`); } const tokenNull = Symbol(); function scalarFromJson(field, json, nullAsZeroValue) { if (json === null) { if (nullAsZeroValue) { return (0, scalar_js_1.scalarZeroValue)(field.scalar, false); } return tokenNull; } // int64, sfixed64, sint64, fixed64, uint64: Reflect supports string and number. // string, bool: Supported by reflect. // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check switch (field.scalar) { // 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 descriptors_js_1.ScalarType.DOUBLE: case descriptors_js_1.ScalarType.FLOAT: if (json === "NaN") return NaN; if (json === "Infinity") return Number.POSITIVE_INFINITY; if (json === "-Infinity") return Number.NEGATIVE_INFINITY; if (typeof json == "number") { if (isNaN(json)) { // NaN must be encoded with string constants throw new error_js_1.FieldError(field, "unexpected NaN number"); } if (!isFinite(json)) { // Infinity must be encoded with string constants throw new error_js_1.FieldError(field, "unexpected infinite number"); } break; } if (typeof json == "string") { if (json === "") { // empty string is not a number break; } if (json.trim().length !== json.length) { // extra whitespace break; } const float = Number(json); if (!isFinite(float)) { // Infinity and NaN must be encoded with string constants break; } return float; } break; // int32, fixed32, uint32: JSON value will be a decimal number. Either numbers or strings are accepted. case descriptors_js_1.ScalarType.INT32: case descriptors_js_1.ScalarType.FIXED32: case descriptors_js_1.ScalarType.SFIXED32: case descriptors_js_1.ScalarType.SINT32: case descriptors_js_1.ScalarType.UINT32: return int32FromJson(json); // 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 descriptors_js_1.ScalarType.BYTES: if (typeof json == "string") { if (json === "") { return new Uint8Array(0); } try { return (0, base64_encoding_js_1.base64Decode)(json); } catch (e) { const message = e instanceof Error ? e.message : String(e); throw new error_js_1.FieldError(field, message); } } break; } return json; } /** * Try to parse a JSON value to a map key for the reflect API. * * Returns the input if the JSON value cannot be converted. */ function mapKeyFromJson(type, json) { switch (type) { case descriptors_js_1.ScalarType.BOOL: // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check switch (json) { case "true": return true; case "false": return false; } return json; case descriptors_js_1.ScalarType.INT32: case descriptors_js_1.ScalarType.FIXED32: case descriptors_js_1.ScalarType.UINT32: case descriptors_js_1.ScalarType.SFIXED32: case descriptors_js_1.ScalarType.SINT32: return int32FromJson(json); default: return json; } } /** * Try to parse a JSON value to a 32-bit integer for the reflect API. * * Returns the input if the JSON value cannot be converted. */ function int32FromJson(json) { if (typeof json == "string") { if (json === "") { // empty string is not a number return json; } if (json.trim().length !== json.length) { // extra whitespace return json; } const num = Number(json); if (Number.isNaN(num)) { // not a number return json; } return num; } return json; } function parseJsonString(jsonString, typeName) { try { return JSON.parse(jsonString); } catch (e) { const message = e instanceof Error ? e.message : String(e); throw new Error(`cannot decode message ${typeName} from JSON: ${message}`, // @ts-expect-error we use the ES2022 error CTOR option "cause" for better stack traces { cause: e }); } } function tryWktFromJson(msg, jsonValue, opts) { if (!msg.desc.typeName.startsWith("google.protobuf.")) { return false; } switch (msg.desc.typeName) { case "google.protobuf.Any": anyFromJson(msg.message, jsonValue, opts); return true; case "google.protobuf.Timestamp": timestampFromJson(msg.message, jsonValue); return true; case "google.protobuf.Duration": durationFromJson(msg.message, jsonValue); return true; case "google.protobuf.FieldMask": fieldMaskFromJson(msg.message, jsonValue); return true; case "google.protobuf.Struct": structFromJson(msg.message, jsonValue); return true; case "google.protobuf.Value": valueFromJson(msg.message, jsonValue); return true; case "google.protobuf.ListValue": listValueFromJson(msg.message, jsonValue); return true; default: if ((0, index_js_1.isWrapperDesc)(msg.desc)) { const valueField = msg.desc.fields[0]; if (jsonValue === null) { msg.clear(valueField); } else { msg.set(valueField, scalarFromJson(valueField, jsonValue, true)); } return true; } return false; } } function anyFromJson(any, json, opts) { var _a; if (json === null || Array.isArray(json) || typeof json != "object") { throw new Error(`cannot decode message ${any.$typeName} from JSON: expected object but got ${(0, reflect_check_js_1.formatVal)(json)}`); } if (Object.keys(json).length == 0) { return; } const typeUrl = json["@type"]; if (typeof typeUrl != "string" || typeUrl == "") { throw new Error(`cannot decode message ${any.$typeName} from JSON: "@type" is empty`); } const typeName = typeUrl.includes("/") ? typeUrl.substring(typeUrl.lastIndexOf("/") + 1) : typeUrl; if (!typeName.length) { throw new Error(`cannot decode message ${any.$typeName} from JSON: "@type" is invalid`); } const desc = (_a = opts.registry) === null || _a === void 0 ? void 0 : _a.getMessage(typeName); if (!desc) { throw new Error(`cannot decode message ${any.$typeName} from JSON: ${typeUrl} is not in the type registry`); } const msg = (0, reflect_js_1.reflect)(desc); if (typeName.startsWith("google.protobuf.") && Object.prototype.hasOwnProperty.call(json, "value")) { const value = json["value"]; readMessage(msg, value, opts); } else { const copy = Object.assign({}, json); delete copy["@type"]; readMessage(msg, copy, opts); } (0, index_js_1.anyPack)(msg.desc, msg.message, any); } function timestampFromJson(timestamp, json) { if (typeof json !== "string") { throw new Error(`cannot decode message ${timestamp.$typeName} from JSON: ${(0, reflect_check_js_1.formatVal)(json)}`); } const matches = json.match(/^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(?:Z|\.([0-9]{3,9})Z|([+-][0-9][0-9]:[0-9][0-9]))$/); if (!matches) { throw new Error(`cannot decode message ${timestamp.$typeName} from JSON: invalid RFC 3339 string`); } const ms = Date.parse( //prettier-ignore matches[1] + "-" + matches[2] + "-" + matches[3] + "T" + matches[4] + ":" + matches[5] + ":" + matches[6] + (matches[8] ? matches[8] : "Z")); if (Number.isNaN(ms)) { throw new Error(`cannot decode message ${timestamp.$typeName} from JSON: invalid RFC 3339 string`); } if (ms < Date.parse("0001-01-01T00:00:00Z") || ms > Date.parse("9999-12-31T23:59:59Z")) { throw new Error(`cannot decode message ${timestamp.$typeName} from JSON: must be from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z inclusive`); } timestamp.seconds = proto_int64_js_1.protoInt64.parse(ms / 1000); timestamp.nanos = 0; if (matches[7]) { timestamp.nanos = parseInt("1" + matches[7] + "0".repeat(9 - matches[7].length)) - 1000000000; } } function durationFromJson(duration, json) { if (typeof json !== "string") { throw new Error(`cannot decode message ${duration.$typeName} from JSON: ${(0, reflect_check_js_1.formatVal)(json)}`); } const match = json.match(/^(-?[0-9]+)(?:\.([0-9]+))?s/); if (match === null) { throw new Error(`cannot decode message ${duration.$typeName} from JSON: ${(0, reflect_check_js_1.formatVal)(json)}`); } const longSeconds = Number(match[1]); if (longSeconds > 315576000000 || longSeconds < -315576000000) { throw new Error(`cannot decode message ${duration.$typeName} from JSON: ${(0, reflect_check_js_1.formatVal)(json)}`); } duration.seconds = proto_int64_js_1.protoInt64.parse(longSeconds); if (typeof match[2] !== "string") { return; } const nanosStr = match[2] + "0".repeat(9 - match[2].length); duration.nanos = parseInt(nanosStr); if (longSeconds < 0 || Object.is(longSeconds, -0)) { duration.nanos = -duration.nanos; } } function fieldMaskFromJson(fieldMask, json) { if (typeof json !== "string") { throw new Error(`cannot decode message ${fieldMask.$typeName} from JSON: ${(0, reflect_check_js_1.formatVal)(json)}`); } if (json === "") { return; } function camelToSnake(str) { if (str.includes("_")) { throw new Error(`cannot decode message ${fieldMask.$typeName} from JSON: path names must be lowerCamelCase`); } const sc = str.replace(/[A-Z]/g, (letter) => "_" + letter.toLowerCase()); return sc[0] === "_" ? sc.substring(1) : sc; } fieldMask.paths = json.split(",").map(camelToSnake); } function structFromJson(struct, json) { if (typeof json != "object" || json == null || Array.isArray(json)) { throw new Error(`cannot decode message ${struct.$typeName} from JSON ${(0, reflect_check_js_1.formatVal)(json)}`); } for (const [k, v] of Object.entries(json)) { const parsedV = (0, create_js_1.create)(index_js_1.ValueSchema); valueFromJson(parsedV, v); struct.fields[k] = parsedV; } } function valueFromJson(value, json) { switch (typeof json) { case "number": value.kind = { case: "numberValue", value: json }; break; case "string": value.kind = { case: "stringValue", value: json }; break; case "boolean": value.kind = { case: "boolValue", value: json }; break; case "object": if (json === null) { value.kind = { case: "nullValue", value: index_js_1.NullValue.NULL_VALUE }; } else if (Array.isArray(json)) { const listValue = (0, create_js_1.create)(index_js_1.ListValueSchema); listValueFromJson(listValue, json); value.kind = { case: "listValue", value: listValue }; } else { const struct = (0, create_js_1.create)(index_js_1.StructSchema); structFromJson(struct, json); value.kind = { case: "structValue", value: struct }; } break; default: throw new Error(`cannot decode message ${value.$typeName} from JSON ${(0, reflect_check_js_1.formatVal)(json)}`); } return value; } function listValueFromJson(listValue, json) { if (!Array.isArray(json)) { throw new Error(`cannot decode message ${listValue.$typeName} from JSON ${(0, reflect_check_js_1.formatVal)(json)}`); } for (const e of json) { const value = (0, create_js_1.create)(index_js_1.ValueSchema); valueFromJson(value, e); listValue.values.push(value); } }