952 lines
34 KiB
JavaScript
952 lines
34 KiB
JavaScript
// Copyright 2021 Google LLC
|
|
// Copyright 2026 The protobuf.js Authors
|
|
//
|
|
// 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.
|
|
|
|
// Derived from proto3-json-serializer v3.0.4 and modified by The protobuf.js Authors.
|
|
|
|
"use strict";
|
|
var protobuf = require("../light");
|
|
|
|
/* global BigInt */
|
|
|
|
var Type = protobuf.Type,
|
|
Enum = protobuf.Enum,
|
|
util = protobuf.util;
|
|
|
|
var protojson = protobuf.protojson = module.exports = {};
|
|
|
|
function isExtension(field) {
|
|
return Boolean(field.declaringField) || field.name.charAt(0) === ".";
|
|
}
|
|
|
|
function extensionName(field) {
|
|
var df = field.declaringField || field,
|
|
full = df.fullName.charAt(0) === "." ? df.fullName.slice(1) : df.fullName;
|
|
return "[" + (df.protoName ? full.replace(/[^.]+$/, df.protoName) : full) + "]";
|
|
}
|
|
|
|
function indexField(index, key, field, type) {
|
|
var existing = index[key];
|
|
if (existing !== undefined && existing !== field)
|
|
throw Error(type.fullName + ": duplicate ProtoJSON field name " + JSON.stringify(key));
|
|
index[key] = field;
|
|
}
|
|
|
|
function fieldsByJsonName(type) {
|
|
if (type._fieldsByJsonName)
|
|
return type._fieldsByJsonName;
|
|
var index = Object.create(null),
|
|
fields = type.fieldsArray,
|
|
i = 0;
|
|
for (; i < fields.length; ++i) {
|
|
var field = fields[i].resolve();
|
|
if (isExtension(field))
|
|
indexField(index, extensionName(field), field, type);
|
|
else {
|
|
indexField(index, field.name, field, type);
|
|
indexField(index, field.jsonName, field, type);
|
|
indexField(index, field.protoName, field, type);
|
|
}
|
|
}
|
|
type._fieldsByJsonName = index;
|
|
return index;
|
|
}
|
|
|
|
// --- reading scalars ---
|
|
|
|
var INT_RANGE = {
|
|
int32: ["-2147483648", "2147483647"],
|
|
sint32: ["-2147483648", "2147483647"],
|
|
sfixed32: ["-2147483648", "2147483647"],
|
|
uint32: ["0", "4294967295"],
|
|
fixed32: ["0", "4294967295"],
|
|
int64: ["-9223372036854775808", "9223372036854775807"],
|
|
sint64: ["-9223372036854775808", "9223372036854775807"],
|
|
sfixed64: ["-9223372036854775808", "9223372036854775807"],
|
|
uint64: ["0", "18446744073709551615"],
|
|
fixed64: ["0", "18446744073709551615"]
|
|
};
|
|
|
|
var LONG_TYPE = { int64: 1, uint64: 1, sint64: 1, fixed64: 1, sfixed64: 1 };
|
|
|
|
var MAX_FLOAT = 3.4028234663852886e38;
|
|
var hasBigInt = typeof BigInt !== "undefined";
|
|
|
|
var NUMERIC_RE = /^[+-]?(?:[0-9]*\.[0-9]+|[0-9]+\.?)(?:[eE][+-]?[0-9]+)?$/;
|
|
|
|
var SKIP = {};
|
|
|
|
function invalid(name, value, what) {
|
|
return Error(name + ": " + what + ": " + JSON.stringify(value));
|
|
}
|
|
|
|
function parseIntegerString(value, type, name) {
|
|
var str;
|
|
if (typeof value === "number") {
|
|
if (!isFinite(value) || Math.floor(value) !== value)
|
|
throw invalid(name, value, "not an integer");
|
|
str = numberToIntString(value);
|
|
} else if (typeof value === "string") {
|
|
if (value.length === 0 || /^\s|\s$/.test(value))
|
|
throw invalid(name, value, "invalid integer");
|
|
if (/^[+-]?[0-9]+$/.test(value))
|
|
str = value;
|
|
else if (NUMERIC_RE.test(value)) {
|
|
var num = Number(value);
|
|
if (!isFinite(num) || Math.floor(num) !== num)
|
|
throw invalid(name, value, "not an integer");
|
|
str = numberToIntString(num);
|
|
} else
|
|
throw invalid(name, value, "invalid integer");
|
|
} else
|
|
throw invalid(name, value, "expected integer (number or string)");
|
|
|
|
var range = INT_RANGE[type];
|
|
if (LONG_TYPE[type]) {
|
|
if (hasBigInt) {
|
|
var big = BigInt(str);
|
|
if (big < BigInt(range[0]) || big > BigInt(range[1]))
|
|
throw invalid(name, value, "out of range for " + type);
|
|
}
|
|
} else if (Number(str) < Number(range[0]) || Number(str) > Number(range[1]))
|
|
throw invalid(name, value, "out of range for " + type);
|
|
return str;
|
|
}
|
|
|
|
function parseMapIntegerKey(key, type, name) {
|
|
var unsigned = type === "uint32" || type === "fixed32" || type === "uint64" || type === "fixed64";
|
|
if (!(unsigned ? /^[0-9]+$/ : /^-?[0-9]+$/).test(key))
|
|
throw invalid(name, key, "invalid " + type + " map key");
|
|
if (hasBigInt) {
|
|
var big = BigInt(key),
|
|
range = INT_RANGE[type];
|
|
if (big < BigInt(range[0]) || big > BigInt(range[1]))
|
|
throw invalid(name, key, "out of range for " + type + " map key");
|
|
return big.toString();
|
|
}
|
|
parseIntegerString(key, type, name);
|
|
if (LONG_TYPE[type]) {
|
|
var normalized = key.replace(/^-?0+(?=\d)/, key.charAt(0) === "-" ? "-" : "");
|
|
return normalized === "-0" ? "0" : normalized;
|
|
}
|
|
return String(Number(key));
|
|
}
|
|
|
|
function numberToIntString(num) {
|
|
if (num >= -9007199254740991 && num <= 9007199254740991)
|
|
return String(num);
|
|
return num.toFixed(0);
|
|
}
|
|
|
|
function parseFloat32Or64(value, isFloat, name) {
|
|
var num;
|
|
if (typeof value === "number") {
|
|
if (!isFinite(value))
|
|
throw invalid(name, value, "number out of range");
|
|
num = value;
|
|
} else if (value === "NaN")
|
|
return NaN;
|
|
else if (value === "Infinity")
|
|
return Infinity;
|
|
else if (value === "-Infinity")
|
|
return -Infinity;
|
|
else if (typeof value === "string") {
|
|
if (value.length === 0 || /^\s|\s$/.test(value) || !NUMERIC_RE.test(value))
|
|
throw invalid(name, value, "invalid number");
|
|
num = Number(value);
|
|
if (!isFinite(num))
|
|
throw invalid(name, value, "invalid number");
|
|
} else
|
|
throw invalid(name, value, "expected number");
|
|
if (isFloat && Math.abs(num) > MAX_FLOAT)
|
|
throw invalid(name, value, "out of range for float");
|
|
return num;
|
|
}
|
|
|
|
function validateUtf16(value, name) {
|
|
for (var i = 0; i < value.length; ++i) {
|
|
var c = value.charCodeAt(i);
|
|
if (c >= 0xD800 && c <= 0xDBFF) {
|
|
var next = value.charCodeAt(i + 1);
|
|
if (!(next >= 0xDC00 && next <= 0xDFFF))
|
|
throw invalid(name, value, "unpaired high surrogate");
|
|
++i;
|
|
} else if (c >= 0xDC00 && c <= 0xDFFF)
|
|
throw invalid(name, value, "unpaired low surrogate");
|
|
}
|
|
}
|
|
|
|
function parseBytes(value, name) {
|
|
if (typeof value !== "string")
|
|
throw invalid(name, value, "expected base64 string");
|
|
var s = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
while (s.length % 4)
|
|
s += "=";
|
|
var buffer = util.newBuffer(util.base64.length(s));
|
|
util.base64.decode(s, buffer, 0);
|
|
return buffer;
|
|
}
|
|
|
|
function longFromString(str, unsigned) {
|
|
return util.Long ? util.Long.fromString(str, unsigned) : parseInt(str, 10);
|
|
}
|
|
|
|
function readScalar(type, value, name) {
|
|
switch (type) {
|
|
case "int32": case "sint32": case "sfixed32":
|
|
case "uint32": case "fixed32":
|
|
return Number(parseIntegerString(value, type, name));
|
|
case "int64": case "sint64": case "sfixed64":
|
|
return longFromString(parseIntegerString(value, type, name), false);
|
|
case "uint64": case "fixed64":
|
|
return longFromString(parseIntegerString(value, type, name), true);
|
|
case "float":
|
|
return parseFloat32Or64(value, true, name);
|
|
case "double":
|
|
return parseFloat32Or64(value, false, name);
|
|
case "bool":
|
|
if (typeof value !== "boolean")
|
|
throw invalid(name, value, "expected boolean");
|
|
return value;
|
|
case "string":
|
|
if (typeof value !== "string")
|
|
throw invalid(name, value, "expected string");
|
|
validateUtf16(value, name);
|
|
return value;
|
|
case "bytes":
|
|
return parseBytes(value, name);
|
|
default:
|
|
throw Error(name + ": unsupported scalar type " + type);
|
|
}
|
|
}
|
|
|
|
function readEnum(enm, value, name, options) {
|
|
if (typeof value === "string") {
|
|
var num = enm.values[value];
|
|
if (num !== undefined)
|
|
return num;
|
|
if (options.ignoreUnknownFields)
|
|
return SKIP;
|
|
throw invalid(name, value, "unknown enum value");
|
|
}
|
|
if (typeof value === "number") {
|
|
if ((value | 0) !== value)
|
|
throw invalid(name, value, "invalid enum number");
|
|
if (enm._features.enum_type === "CLOSED" && enm.valuesById[value] === undefined)
|
|
throw invalid(name, value, "unknown enum value");
|
|
return value;
|
|
}
|
|
if (value === null && enm.fullName === ".google.protobuf.NullValue")
|
|
return 0;
|
|
throw invalid(name, value, "expected enum string or number");
|
|
}
|
|
|
|
function readMapKey(field, key) {
|
|
switch (field.keyType) {
|
|
case "bool":
|
|
if (key !== "true" && key !== "false")
|
|
throw invalid(field.fullName, key, "invalid bool map key");
|
|
return key;
|
|
case "string":
|
|
validateUtf16(key, field.fullName);
|
|
return key;
|
|
case "int32": case "sint32": case "sfixed32":
|
|
case "uint32": case "fixed32":
|
|
case "int64": case "sint64": case "sfixed64":
|
|
case "uint64": case "fixed64":
|
|
return parseMapIntegerKey(key, field.keyType, field.fullName);
|
|
default:
|
|
throw Error(field.fullName + ": unsupported map key type " + field.keyType);
|
|
}
|
|
}
|
|
|
|
// --- reading messages ---
|
|
|
|
function readField(field, value, options, depth) {
|
|
if (field.map) {
|
|
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
throw invalid(field.fullName, value, "expected object for map");
|
|
var map = Object.create(null), k;
|
|
for (k in value)
|
|
if (hasOwn(value, k)) {
|
|
var mk = readMapKey(field, k),
|
|
mv = readSingular(field, value[k], options, depth);
|
|
if (mv !== SKIP) {
|
|
if (hasOwn(map, mk))
|
|
throw invalid(field.fullName, k, "duplicate map key");
|
|
map[mk] = mv;
|
|
}
|
|
}
|
|
return map;
|
|
}
|
|
if (field.repeated) {
|
|
if (!Array.isArray(value))
|
|
throw invalid(field.fullName, value, "expected array");
|
|
var arr = [], i = 0;
|
|
for (; i < value.length; ++i) {
|
|
if (value[i] === null && !isValueType(field.resolvedType) && !isNullValueType(field.resolvedType))
|
|
throw invalid(field.fullName, null, "null element");
|
|
var el = readSingular(field, value[i], options, depth);
|
|
if (el !== SKIP)
|
|
arr.push(el);
|
|
}
|
|
return arr;
|
|
}
|
|
return readSingular(field, value, options, depth);
|
|
}
|
|
|
|
function readSingular(field, value, options, depth) {
|
|
if (field.resolvedType instanceof Type)
|
|
return readMessage(field.resolvedType, value, options, depth + 1);
|
|
if (field.resolvedType instanceof Enum)
|
|
return readEnum(field.resolvedType, value, field.fullName, options);
|
|
return readScalar(field.type, value, field.fullName);
|
|
}
|
|
|
|
function isValueType(type) {
|
|
return type instanceof Type && type.fullName === ".google.protobuf.Value";
|
|
}
|
|
|
|
function isNullValueType(type) {
|
|
return type instanceof Enum && type.fullName === ".google.protobuf.NullValue";
|
|
}
|
|
|
|
function isImplicitDefault(field, value) {
|
|
if (field.hasPresence || field.repeated || field.map || field.resolvedType instanceof Type)
|
|
return false;
|
|
if (field.resolvedType instanceof Enum)
|
|
return value === 0;
|
|
switch (field.type) {
|
|
case "bool": return value === false;
|
|
case "string": return value === "";
|
|
case "bytes": return value == null || value.length === 0;
|
|
default: return longToNumber(value) === 0;
|
|
}
|
|
}
|
|
|
|
function readMessage(type, value, options, depth) {
|
|
if (depth > util.recursionLimit)
|
|
throw Error("max depth exceeded");
|
|
|
|
var wkt = WKT_FROM[type.fullName];
|
|
if (wkt)
|
|
return wkt(type, value, options, depth);
|
|
|
|
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
throw invalid(type.fullName, value, "expected object");
|
|
|
|
var index = fieldsByJsonName(type),
|
|
out = {},
|
|
seenFields = Object.create(null),
|
|
seenOneofs = Object.create(null),
|
|
key;
|
|
|
|
for (key in value) {
|
|
if (!hasOwn(value, key))
|
|
continue;
|
|
var field = index[key];
|
|
if (field === undefined) {
|
|
if (options.ignoreUnknownFields)
|
|
continue;
|
|
throw invalid(type.fullName, key, "unknown field");
|
|
}
|
|
if (seenFields[field.name])
|
|
throw invalid(type.fullName, key, "duplicate field");
|
|
seenFields[field.name] = true;
|
|
var fv = value[key], fieldValue;
|
|
if (fv === null) {
|
|
if (isValueType(field.resolvedType))
|
|
fieldValue = { nullValue: 0 };
|
|
else if (field.resolvedType instanceof Enum && field.resolvedType.fullName === ".google.protobuf.NullValue")
|
|
fieldValue = 0;
|
|
else
|
|
continue;
|
|
} else
|
|
fieldValue = readField(field, fv, options, depth);
|
|
if (fieldValue === SKIP)
|
|
continue;
|
|
if (field.partOf) {
|
|
if (seenOneofs[field.partOf.name])
|
|
throw Error(type.fullName + ": multiple values for oneof " + field.partOf.name);
|
|
seenOneofs[field.partOf.name] = true;
|
|
}
|
|
if (!isImplicitDefault(field, fieldValue))
|
|
out[field.name] = fieldValue;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
// --- writing messages ---
|
|
|
|
function hasOwn(o, k) {
|
|
return o != null && Object.prototype.hasOwnProperty.call(o, k);
|
|
}
|
|
|
|
function setOwn(o, k, v) {
|
|
if (k === "__proto__")
|
|
util.makeProp(o, k);
|
|
o[k] = v;
|
|
}
|
|
|
|
function wktFieldName(type, name) {
|
|
var field = fieldsByJsonName(type)[name];
|
|
return field ? field.name : name;
|
|
}
|
|
|
|
function wktFieldValue(type, message, name) {
|
|
var field = fieldsByJsonName(type)[name];
|
|
if (!field)
|
|
return message && message[name];
|
|
if (hasOwn(message, field.name))
|
|
return message[field.name];
|
|
if (hasOwn(message, field.protoName))
|
|
return message[field.protoName];
|
|
if (hasOwn(message, field.jsonName))
|
|
return message[field.jsonName];
|
|
return undefined;
|
|
}
|
|
|
|
function writeScalar(type, value) {
|
|
switch (type) {
|
|
case "int64": case "sint64": case "sfixed64":
|
|
case "uint64": case "fixed64":
|
|
return value == null ? "0" : String(value);
|
|
case "float": case "double":
|
|
return typeof value === "number" && !isFinite(value) ? String(value) : value;
|
|
case "bytes":
|
|
return value == null ? "" : util.base64.encode(value, 0, value.length);
|
|
default:
|
|
return value;
|
|
}
|
|
}
|
|
|
|
function writeSingular(field, value, options, depth) {
|
|
if (field.resolvedType instanceof Type)
|
|
return toJsonValue(field.resolvedType, value, options, depth + 1);
|
|
if (field.resolvedType instanceof Enum) {
|
|
if (field.resolvedType.fullName === ".google.protobuf.NullValue")
|
|
return null;
|
|
var name = field.resolvedType.valuesById[value];
|
|
return name === undefined ? value : name;
|
|
}
|
|
return writeScalar(field.type, value);
|
|
}
|
|
|
|
function toJsonValue(type, message, options, depth) {
|
|
if (depth > util.recursionLimit)
|
|
throw Error("max depth exceeded");
|
|
|
|
var wkt = WKT_TO[type.fullName];
|
|
if (wkt)
|
|
return wkt(type, message, options, depth);
|
|
|
|
var out = {},
|
|
fields = type.fieldsArray,
|
|
i = 0;
|
|
for (; i < fields.length; ++i) {
|
|
var field = fields[i].resolve(),
|
|
value = message[field.name];
|
|
if (value == null)
|
|
continue;
|
|
var key = isExtension(field) ? extensionName(field) : field.jsonName;
|
|
if (field.map) {
|
|
var mk = Object.keys(value);
|
|
if (!mk.length)
|
|
continue;
|
|
var longKey = LONG_TYPE[field.keyType],
|
|
unsignedKey = field.keyType === "uint64" || field.keyType === "fixed64",
|
|
mo = {}, ki = 0;
|
|
for (; ki < mk.length; ++ki) {
|
|
var outKey = longKey ? util.longFromKey(mk[ki], unsignedKey).toString() : mk[ki];
|
|
setOwn(mo, outKey, writeSingular(field, value[mk[ki]], options, depth));
|
|
}
|
|
setOwn(out, key, mo);
|
|
} else if (field.repeated) {
|
|
if (!value.length)
|
|
continue;
|
|
var arr = new Array(value.length), j = 0;
|
|
for (; j < value.length; ++j)
|
|
arr[j] = writeSingular(field, value[j], options, depth);
|
|
setOwn(out, key, arr);
|
|
} else {
|
|
if (!hasOwn(message, field.name) || isImplicitDefault(field, value))
|
|
continue;
|
|
setOwn(out, key, writeSingular(field, value, options, depth));
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
// --- well-known types ---
|
|
|
|
function longToNumber(value) {
|
|
if (value == null) return 0;
|
|
if (typeof value === "number") return value;
|
|
if (typeof value.toNumber === "function") return value.toNumber();
|
|
return Number(value) || 0;
|
|
}
|
|
|
|
function nanosToString(nanos) {
|
|
var str = String(nanos < 0 ? -nanos : nanos);
|
|
while (str.length < 9) str = "0" + str;
|
|
while (str.length > 3 && str.slice(str.length - 3) === "000") str = str.slice(0, str.length - 3);
|
|
return str;
|
|
}
|
|
|
|
function fracToNanos(frac) {
|
|
while (frac.length < 9) frac += "0";
|
|
return parseInt(frac.slice(0, 9), 10);
|
|
}
|
|
|
|
function daysInMonth(year, month) {
|
|
switch (month) {
|
|
case 2:
|
|
return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) ? 29 : 28;
|
|
case 4:
|
|
case 6:
|
|
case 9:
|
|
case 11:
|
|
return 30;
|
|
default:
|
|
return 31;
|
|
}
|
|
}
|
|
|
|
function camelToSnake(path) {
|
|
return path.replace(/[A-Z]/g, function ($0) { return "_" + $0.toLowerCase(); });
|
|
}
|
|
|
|
var WKT_FROM = {};
|
|
var WKT_TO = {};
|
|
|
|
WKT_FROM[".google.protobuf.Duration"] = function (type, value) {
|
|
if (typeof value !== "string")
|
|
throw invalid(type.fullName, value, "expected duration string");
|
|
var match = /^(-)?([0-9]+)(?:\.([0-9]{1,9}))?s$/.exec(value);
|
|
if (!match)
|
|
throw invalid(type.fullName, value, "invalid duration");
|
|
var sign = match[1] ? -1 : 1,
|
|
seconds = parseInt(match[2], 10) * sign || 0,
|
|
nanos = match[3] ? fracToNanos(match[3]) * sign || 0 : 0;
|
|
if (seconds > 315576000000 || seconds < -315576000000)
|
|
throw invalid(type.fullName, value, "duration out of range");
|
|
var message = {};
|
|
if (seconds !== 0)
|
|
message.seconds = seconds;
|
|
if (nanos !== 0)
|
|
message.nanos = nanos;
|
|
return message;
|
|
};
|
|
WKT_TO[".google.protobuf.Duration"] = function (type, message) {
|
|
var seconds = longToNumber(message.seconds),
|
|
nanos = message.nanos || 0;
|
|
if (seconds > 315576000000 || seconds < -315576000000)
|
|
throw Error("google.protobuf.Duration out of range");
|
|
if (nanos > 999999999 || nanos < -999999999 || seconds && nanos && seconds < 0 !== nanos < 0)
|
|
throw Error("google.protobuf.Duration nanos invalid");
|
|
var result = (seconds < 0 || nanos < 0 ? "-" : "") + Math.abs(seconds);
|
|
if (nanos)
|
|
result += "." + nanosToString(nanos);
|
|
return result + "s";
|
|
};
|
|
|
|
WKT_FROM[".google.protobuf.Timestamp"] = function (type, value) {
|
|
if (typeof value !== "string")
|
|
throw invalid(type.fullName, value, "expected timestamp string");
|
|
var match = /^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])T([01]\d|2[0-3]):([0-5]\d):([0-5]\d)(?:\.(\d{1,9}))?(Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/.exec(value);
|
|
if (!match)
|
|
throw invalid(type.fullName, value, "invalid timestamp");
|
|
var year = parseInt(match[1], 10),
|
|
month = parseInt(match[2], 10),
|
|
day = parseInt(match[3], 10);
|
|
if (day > daysInMonth(year, month))
|
|
throw invalid(type.fullName, value, "invalid timestamp date");
|
|
var millis = new Date(value).getTime();
|
|
if (isNaN(millis))
|
|
throw invalid(type.fullName, value, "invalid timestamp");
|
|
var seconds = Math.floor(millis / 1000),
|
|
nanos = match[7] ? fracToNanos(match[7]) : 0;
|
|
if (seconds < -62135596800 || seconds > 253402300799)
|
|
throw invalid(type.fullName, value, "timestamp out of range");
|
|
var message = {};
|
|
if (seconds !== 0)
|
|
message.seconds = seconds;
|
|
if (nanos !== 0)
|
|
message.nanos = nanos;
|
|
return message;
|
|
};
|
|
WKT_TO[".google.protobuf.Timestamp"] = function (type, message) {
|
|
var seconds = longToNumber(message.seconds),
|
|
nanos = message.nanos || 0;
|
|
if (seconds < -62135596800 || seconds > 253402300799)
|
|
throw Error("google.protobuf.Timestamp out of range");
|
|
if (nanos < 0 || nanos > 999999999)
|
|
throw Error("google.protobuf.Timestamp nanos out of range");
|
|
var iso = new Date(seconds * 1000).toISOString();
|
|
return nanos
|
|
? iso.replace(/\.\d+Z$/, "." + nanosToString(nanos) + "Z")
|
|
: iso.replace(/\.\d+Z$/, "Z");
|
|
};
|
|
|
|
WKT_FROM[".google.protobuf.FieldMask"] = function (type, value) {
|
|
if (typeof value !== "string")
|
|
throw invalid(type.fullName, value, "expected field mask string");
|
|
if (value.indexOf("_") !== -1)
|
|
throw invalid(type.fullName, value, "field mask path must be lowerCamelCase");
|
|
var paths = value.length ? value.split(",") : [],
|
|
i = 0;
|
|
for (; i < paths.length; ++i)
|
|
paths[i] = camelToSnake(paths[i]);
|
|
return { paths: paths };
|
|
};
|
|
WKT_TO[".google.protobuf.FieldMask"] = function (type, message) {
|
|
var paths = message.paths || [],
|
|
out = [],
|
|
i = 0;
|
|
for (; i < paths.length; ++i) {
|
|
var camel = util.jsonName(paths[i]);
|
|
if (camelToSnake(camel) !== paths[i])
|
|
throw Error("google.protobuf.FieldMask path does not round-trip: " + paths[i]);
|
|
out.push(camel);
|
|
}
|
|
return out.join(",");
|
|
};
|
|
|
|
["DoubleValue", "FloatValue", "Int64Value", "UInt64Value", "Int32Value",
|
|
"UInt32Value", "BoolValue", "StringValue", "BytesValue"].forEach(function (name) {
|
|
var fullName = ".google.protobuf." + name;
|
|
WKT_FROM[fullName] = function (type, value, options, depth) {
|
|
var field = type.fields.value.resolve(),
|
|
fieldValue = readSingular(field, value, options, depth),
|
|
message = {};
|
|
if (!isImplicitDefault(field, fieldValue))
|
|
message.value = fieldValue;
|
|
return message;
|
|
};
|
|
WKT_TO[fullName] = function (type, message, options, depth) {
|
|
var field = type.fields.value.resolve(),
|
|
value = wktFieldValue(type, message, "value");
|
|
return writeSingular(field, value === undefined ? field.defaultValue : value, options, depth);
|
|
};
|
|
});
|
|
|
|
function valueFromJson(json, depth) {
|
|
if (depth > util.recursionLimit)
|
|
throw Error("max depth exceeded");
|
|
if (json === null)
|
|
return { nullValue: 0 };
|
|
switch (typeof json) {
|
|
case "number":
|
|
if (!isFinite(json))
|
|
throw Error("google.protobuf.Value cannot hold a non-finite number");
|
|
return { numberValue: json };
|
|
case "string":
|
|
validateUtf16(json, "google.protobuf.Value.string_value");
|
|
return { stringValue: json };
|
|
case "boolean":
|
|
return { boolValue: json };
|
|
}
|
|
if (Array.isArray(json)) {
|
|
var values = new Array(json.length), i = 0;
|
|
for (; i < json.length; ++i)
|
|
values[i] = valueFromJson(json[i], depth + 1);
|
|
return { listValue: { values: values } };
|
|
}
|
|
return { structValue: { fields: structFieldsFromJson(json, depth + 1) } };
|
|
}
|
|
function structFieldsFromJson(json, depth) {
|
|
var fields = Object.create(null), k;
|
|
for (k in json)
|
|
if (hasOwn(json, k)) {
|
|
validateUtf16(k, "google.protobuf.Struct.fields");
|
|
fields[k] = valueFromJson(json[k], depth);
|
|
}
|
|
return fields;
|
|
}
|
|
WKT_FROM[".google.protobuf.Value"] = function (type, value, options, depth) {
|
|
return valueFromJson(value, depth);
|
|
};
|
|
WKT_FROM[".google.protobuf.Struct"] = function (type, value, options, depth) {
|
|
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
throw invalid(type.fullName, value, "google.protobuf.Struct must be an object");
|
|
return { fields: structFieldsFromJson(value, depth + 1) };
|
|
};
|
|
WKT_FROM[".google.protobuf.ListValue"] = function (type, value, options, depth) {
|
|
if (!Array.isArray(value))
|
|
throw invalid(type.fullName, value, "google.protobuf.ListValue must be an array");
|
|
var values = new Array(value.length), i = 0;
|
|
for (; i < value.length; ++i)
|
|
values[i] = valueFromJson(value[i], depth + 1);
|
|
return { values: values };
|
|
};
|
|
|
|
function valueToJson(message, options, depth) {
|
|
if (depth > util.recursionLimit)
|
|
throw Error("max depth exceeded");
|
|
if (message == null || hasOwn(message, "nullValue"))
|
|
return null;
|
|
if (hasOwn(message, "numberValue")) {
|
|
if (typeof message.numberValue === "number" && !isFinite(message.numberValue))
|
|
throw Error("google.protobuf.Value cannot hold a non-finite number");
|
|
return message.numberValue;
|
|
}
|
|
if (hasOwn(message, "stringValue")) return message.stringValue;
|
|
if (hasOwn(message, "boolValue")) return message.boolValue;
|
|
if (hasOwn(message, "structValue")) return structToJson(message.structValue, options, depth + 1);
|
|
if (hasOwn(message, "listValue")) return listToJson(message.listValue, options, depth + 1);
|
|
return null;
|
|
}
|
|
function structToJson(message, options, depth) {
|
|
var out = {}, fields = message && message.fields, k;
|
|
if (fields)
|
|
for (k in fields)
|
|
if (hasOwn(fields, k))
|
|
setOwn(out, k, valueToJson(fields[k], options, depth));
|
|
return out;
|
|
}
|
|
function listToJson(message, options, depth) {
|
|
var values = message && message.values;
|
|
if (!values) return [];
|
|
var arr = new Array(values.length), i = 0;
|
|
for (; i < values.length; ++i)
|
|
arr[i] = valueToJson(values[i], options, depth);
|
|
return arr;
|
|
}
|
|
WKT_TO[".google.protobuf.Value"] = function (type, message, options, depth) {
|
|
return valueToJson(message, options, depth);
|
|
};
|
|
WKT_TO[".google.protobuf.Struct"] = function (type, message, options, depth) {
|
|
return structToJson(message, options, depth);
|
|
};
|
|
WKT_TO[".google.protobuf.ListValue"] = function (type, message, options, depth) {
|
|
return listToJson(message, options, depth);
|
|
};
|
|
|
|
WKT_FROM[".google.protobuf.Any"] = function (type, value, options, depth) {
|
|
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
throw invalid(type.fullName, value, "google.protobuf.Any must be an object");
|
|
var typeUrl = value["@type"];
|
|
if (typeUrl === undefined)
|
|
return {};
|
|
if (typeof typeUrl !== "string")
|
|
throw Error("google.protobuf.Any @type must be a string");
|
|
var name = typeUrl.substring(typeUrl.lastIndexOf("/") + 1),
|
|
msgType = type.root.lookupType(name),
|
|
custom = WKT_FROM[msgType.fullName] !== undefined,
|
|
body;
|
|
if (custom)
|
|
body = value.value;
|
|
else {
|
|
body = {};
|
|
for (var k in value)
|
|
if (hasOwn(value, k) && k !== "@type")
|
|
setOwn(body, k, value[k]);
|
|
}
|
|
var inner = readMessage(msgType, body, options, depth + 1);
|
|
var url = typeUrl.charAt(0) === "." ? typeUrl.slice(1) : typeUrl,
|
|
out = {};
|
|
if (url.indexOf("/") === -1)
|
|
url = "/" + url;
|
|
out[wktFieldName(type, "type_url")] = url;
|
|
var bytes = msgType.encode(inner).finish();
|
|
if (bytes.length)
|
|
out[wktFieldName(type, "value")] = bytes;
|
|
return out;
|
|
};
|
|
WKT_TO[".google.protobuf.Any"] = function (type, message, options, depth) {
|
|
var typeUrl = wktFieldValue(type, message, "type_url");
|
|
if (!typeUrl)
|
|
return {};
|
|
var name = typeUrl.substring(typeUrl.lastIndexOf("/") + 1),
|
|
msgType = type.root.lookupType(name),
|
|
value = wktFieldValue(type, message, "value"),
|
|
decoded = msgType.decode(value || util.emptyArray),
|
|
body = toJsonValue(msgType, decoded, options, depth + 1),
|
|
result;
|
|
if (WKT_TO[msgType.fullName])
|
|
result = { "@type": typeUrl, "value": body };
|
|
else {
|
|
result = { "@type": typeUrl };
|
|
for (var k in body)
|
|
if (hasOwn(body, k))
|
|
setOwn(result, k, body[k]);
|
|
}
|
|
return result;
|
|
};
|
|
|
|
// --- duplicate keys ---
|
|
|
|
// JSON.parse keeps the last duplicate key, but ProtoJSON rejects duplicates.
|
|
function checkDuplicateKeys(str) {
|
|
var stack = [],
|
|
expectKey = false,
|
|
i = 0,
|
|
n = str.length;
|
|
while (i < n) {
|
|
var c = str.charAt(i);
|
|
if (c === "{") {
|
|
stack.push(Object.create(null));
|
|
expectKey = true;
|
|
++i;
|
|
} else if (c === "[") {
|
|
stack.push(null);
|
|
expectKey = false;
|
|
++i;
|
|
} else if (c === "}" || c === "]") {
|
|
stack.pop();
|
|
expectKey = false;
|
|
++i;
|
|
} else if (c === ":") {
|
|
expectKey = false;
|
|
++i;
|
|
} else if (c === ",") {
|
|
expectKey = stack.length > 0 && stack[stack.length - 1] !== null;
|
|
++i;
|
|
} else if (c === "\"") {
|
|
var start = i++;
|
|
while (i < n) {
|
|
var ch = str.charAt(i++);
|
|
if (ch === "\\") ++i;
|
|
else if (ch === "\"") break;
|
|
}
|
|
if (expectKey) {
|
|
var seen = stack[stack.length - 1],
|
|
name = JSON.parse(str.slice(start, i));
|
|
if (seen[name])
|
|
throw Error("duplicate key in JSON object: " + JSON.stringify(name));
|
|
seen[name] = true;
|
|
expectKey = false;
|
|
}
|
|
} else
|
|
++i;
|
|
}
|
|
}
|
|
|
|
// --- public API ---
|
|
|
|
/**
|
|
* ProtoJSON conversion options.
|
|
* @interface IProtoJsonOptions
|
|
* @property {boolean} [ignoreUnknownFields=false] Ignores unknown object members and unrecognized enum names while parsing.
|
|
*/
|
|
|
|
/**
|
|
* Parses a message from an already-parsed ProtoJSON value using the specified reflected type.
|
|
* @function fromJson
|
|
* @name fromJson
|
|
* @param {$protobuf.Type} type Reflected message type
|
|
* @param {*} json Already-parsed ProtoJSON value
|
|
* @param {IProtoJsonOptions} [options] Conversion options
|
|
* @returns {$protobuf.Message<{}>} Message instance
|
|
*/
|
|
protojson.fromJson = function fromJson(type, json, options) {
|
|
if (!(type instanceof Type))
|
|
throw TypeError("type must be a Type");
|
|
type.root.resolveAll();
|
|
return type.create(readMessage(type, json, options || {}, 0));
|
|
};
|
|
|
|
/**
|
|
* Parses a message from ProtoJSON text using the specified reflected type.
|
|
* @function fromJsonString
|
|
* @name fromJsonString
|
|
* @param {$protobuf.Type} type Reflected message type
|
|
* @param {string} json ProtoJSON text
|
|
* @param {IProtoJsonOptions} [options] Conversion options
|
|
* @returns {$protobuf.Message<{}>} Message instance
|
|
*/
|
|
protojson.fromJsonString = function fromJsonString(type, json, options) {
|
|
if (typeof json !== "string")
|
|
throw TypeError("json must be a string");
|
|
checkDuplicateKeys(json);
|
|
return protojson.fromJson(type, JSON.parse(json), options);
|
|
};
|
|
|
|
/**
|
|
* Formats a message as ProtoJSON using the specified reflected type.
|
|
* @function toJson
|
|
* @name toJson
|
|
* @param {$protobuf.Type} type Reflected message type
|
|
* @param {$protobuf.Message<{}>|Object.<string,*>} message Message instance or plain object
|
|
* @param {IProtoJsonOptions} [options] Conversion options
|
|
* @returns {*} ProtoJSON value (object, array, string, number, boolean or null)
|
|
*/
|
|
protojson.toJson = function toJson(type, message, options) {
|
|
if (!(type instanceof Type))
|
|
throw TypeError("type must be a Type");
|
|
type.root.resolveAll();
|
|
return toJsonValue(type, message, options || {}, 0);
|
|
};
|
|
|
|
/**
|
|
* Formats a message as ProtoJSON text using the specified reflected type.
|
|
* @function toJsonString
|
|
* @name toJsonString
|
|
* @param {$protobuf.Type} type Reflected message type
|
|
* @param {$protobuf.Message<{}>|Object.<string,*>} message Message instance or plain object
|
|
* @param {IProtoJsonOptions} [options] Conversion options
|
|
* @returns {string} ProtoJSON text
|
|
*/
|
|
protojson.toJsonString = function toJsonString(type, message, options) {
|
|
return JSON.stringify(protojson.toJson(type, message, options));
|
|
};
|
|
|
|
/**
|
|
* Installs reflected {@link Type} convenience methods.
|
|
* @function install
|
|
* @name install
|
|
* @returns {undefined}
|
|
*/
|
|
protojson.install = function install() {
|
|
/**
|
|
* Parses a message of this type from an already-parsed ProtoJSON value. Convenience for {@link protojson.fromJson}.
|
|
* @param {*} json Already-parsed ProtoJSON value
|
|
* @param {IProtoJsonOptions} [options] Conversion options
|
|
* @returns {Message<{}>} Message instance
|
|
*/
|
|
Type.prototype.fromJson = function fromJson(json, options) {
|
|
return protojson.fromJson(this, json, options);
|
|
};
|
|
|
|
/**
|
|
* Parses a message of this type from ProtoJSON text. Convenience for {@link protojson.fromJsonString}.
|
|
* @param {string} json ProtoJSON text
|
|
* @param {IProtoJsonOptions} [options] Conversion options
|
|
* @returns {Message<{}>} Message instance
|
|
*/
|
|
Type.prototype.fromJsonString = function fromJsonString(json, options) {
|
|
return protojson.fromJsonString(this, json, options);
|
|
};
|
|
|
|
/**
|
|
* Formats a message of this type as ProtoJSON. Convenience for {@link protojson.toJson}.
|
|
* @param {Message<{}>|Object.<string,*>} message Message instance or plain object
|
|
* @param {IProtoJsonOptions} [options] Conversion options
|
|
* @returns {*} ProtoJSON value
|
|
*/
|
|
Type.prototype.toJson = function toJson(message, options) {
|
|
return protojson.toJson(this, message, options);
|
|
};
|
|
|
|
/**
|
|
* Formats a message of this type as ProtoJSON text. Convenience for {@link protojson.toJsonString}.
|
|
* @param {Message<{}>|Object.<string,*>} message Message instance or plain object
|
|
* @param {IProtoJsonOptions} [options] Conversion options
|
|
* @returns {string} ProtoJSON text
|
|
*/
|
|
Type.prototype.toJsonString = function toJsonString(message, options) {
|
|
return protojson.toJsonString(this, message, options);
|
|
};
|
|
};
|