protobufjs
Version:
Protocol Buffers for JavaScript & TypeScript.
948 lines (874 loc) • 34.7 kB
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.
;
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))
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 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);
};
};