UNPKG

google-closure-library

Version:
267 lines (232 loc) 8.57 kB
/** * @license * Copyright The Closure Library Authors. * SPDX-License-Identifier: Apache-2.0 */ /** * @fileoverview Protocol Buffer 2 Serializer which serializes messages * into anonymous, simplified JSON objects. */ goog.provide('goog.proto2.ObjectSerializer'); goog.require('goog.asserts'); goog.require('goog.proto2.FieldDescriptor'); goog.require('goog.proto2.Serializer'); goog.require('goog.string'); goog.requireType('goog.proto2.Message'); /** * ObjectSerializer, a serializer which turns Messages into simplified * ECMAScript objects. * * @param {goog.proto2.ObjectSerializer.KeyOption=} opt_keyOption If specified, * which key option to use when serializing/deserializing. * @param {boolean=} opt_serializeBooleanAsNumber If specified and true, the * serializer will convert boolean values to 0/1 representation. * @param {boolean=} opt_ignoreUnknownFields If specified and true, the * serializer will ignore unknown fields in the JSON payload instead of * returning an error. * @constructor * @extends {goog.proto2.Serializer} */ goog.proto2.ObjectSerializer = function( opt_keyOption, opt_serializeBooleanAsNumber, opt_ignoreUnknownFields) { 'use strict'; /** @const */ this.keyOption_ = opt_keyOption; /** @const */ this.serializeBooleanAsNumber_ = opt_serializeBooleanAsNumber; /** @const */ this.ignoreUnknownFields_ = opt_ignoreUnknownFields; }; goog.inherits(goog.proto2.ObjectSerializer, goog.proto2.Serializer); /** * An enumeration of the options for how to emit the keys in * the generated simplified object. * * For serialization, the option specifies the keys to use in the serialized * object. * * For deserialization, the option specifies which keys are allowed; an object * serialized by TAG may be deserialized by TAG or by NAME or by * CAMEL_CASE_NAME, but an object serialized by NAME cannot be deserialized by * TAG. An object serialized with any option can be deserialized by * CAMEL_CASE_NAME. * * @enum {number} */ goog.proto2.ObjectSerializer.KeyOption = { /** * Use the tag of the field as the key (default) */ TAG: 0, /** * Use the name of the field as the key. Unknown fields * will still use their tags as keys. */ NAME: 1, /** * Use the camel cased name of the field as the key. * Unknown fields will still use their tags as keys. */ CAMEL_CASE_NAME: 2 }; /** * Serializes a message to an object. * * @param {goog.proto2.Message} message The message to be serialized. * @return {!Object} The serialized form of the message. * @override */ goog.proto2.ObjectSerializer.prototype.serialize = function(message) { 'use strict'; var descriptor = message.getDescriptor(); var fields = descriptor.getFields(); var objectValue = {}; // Add the defined fields, recursively. for (var i = 0; i < fields.length; i++) { var field = fields[i]; var key = field.getTag(); switch (this.keyOption_) { case goog.proto2.ObjectSerializer.KeyOption.TAG: // no action necessary, key already has the correct value. break; case goog.proto2.ObjectSerializer.KeyOption.NAME: key = field.getName(); break; case goog.proto2.ObjectSerializer.KeyOption.CAMEL_CASE_NAME: key = goog.string.toCamelCase( field .getName() // goog.string.toCamelCase expects a hyphen delimited string but // proto fields are usually underscore delimited // (go/proto-style-guide); the following regex converts from // underscore delimited form to hyphen delimited form. .replace(/_/g, '-')); break; default: // Default should never be reached unless keyOption is outside the valid // domain. goog.asserts.assert( this.keyOption_ !== goog.proto2.ObjectSerializer.KeyOption.TAG && this.keyOption_ !== goog.proto2.ObjectSerializer.KeyOption.NAME && this.keyOption_ !== goog.proto2.ObjectSerializer.KeyOption.CAMEL_CASE_NAME, 'keyOption should be one of TAG, NAME, or CAMEL_CASE_NAME'); } if (message.has(field)) { if (field.isRepeated()) { var array = []; objectValue[key] = array; for (var j = 0; j < message.countOf(field); j++) { array.push(this.getSerializedValue(field, message.get(field, j))); } } else { objectValue[key] = this.getSerializedValue(field, message.get(field)); } } } // Add the unknown fields, if any. message.forEachUnknown(function(tag, value) { 'use strict'; // Do not set null values. This is possible when using pbliteserializer to // convert jsbp to closure object and then passed to this method. if (value !== null) { objectValue[tag] = value; } }); return objectValue; }; /** @override */ goog.proto2.ObjectSerializer.prototype.getSerializedValue = function( field, value) { 'use strict'; // Handle the case where a boolean should be serialized as 0/1. // Some deserialization libraries, such as GWT, can use this notation. if (this.serializeBooleanAsNumber_ && field.getFieldType() == goog.proto2.FieldDescriptor.FieldType.BOOL && typeof value === 'boolean') { return value ? 1 : 0; } return goog.proto2.ObjectSerializer.base( this, 'getSerializedValue', field, value); }; /** @override */ goog.proto2.ObjectSerializer.prototype.getDeserializedValue = function( field, value) { 'use strict'; // Gracefully handle the case where a boolean is represented by 0/1. // Some serialization libraries, such as GWT, can use this notation. if (field.getFieldType() == goog.proto2.FieldDescriptor.FieldType.BOOL && typeof value === 'number') { return Boolean(value); } return goog.proto2.ObjectSerializer.base( this, 'getDeserializedValue', field, value); }; /** * Deserializes a message from an object and places the * data in the message. * * @param {goog.proto2.Message} message The message in which to * place the information. * @param {*} data The data of the message. * @override */ goog.proto2.ObjectSerializer.prototype.deserializeTo = function(message, data) { 'use strict'; var descriptor = message.getDescriptor(); for (var key in data) { var field; var value = data[key]; var isNumeric = goog.string.isNumeric(key); if (isNumeric) { field = descriptor.findFieldByTag(key); } else { // We must not be in Key == TAG mode to lookup by name. goog.asserts.assert( this.keyOption_ == goog.proto2.ObjectSerializer.KeyOption.NAME || this.keyOption_ == goog.proto2.ObjectSerializer.KeyOption.CAMEL_CASE_NAME, 'Key mode ' + this.keyOption_ + 'for key ' + key + ' is not ' + goog.proto2.ObjectSerializer.KeyOption.NAME + ' nor ' + goog.proto2.ObjectSerializer.KeyOption.CAMEL_CASE_NAME); if (this.keyOption_ == goog.proto2.ObjectSerializer.KeyOption.CAMEL_CASE_NAME) { key = goog.string .toSelectorCase(key) // goog.string.toSelectorCase returns a hyphen delimited form // of the name but protos usually use an underscore delimited // form (go/proto-style-guide); the following regex converts // from hyphens to underscores. .replace(/\-/g, '_'); } field = descriptor.findFieldByName(key); } if (field) { if (field.isRepeated()) { goog.asserts.assert( Array.isArray(value), 'Value for repeated field ' + field + ' must be an array.'); for (var j = 0; j < value.length; j++) { message.add(field, this.getDeserializedValue(field, value[j])); } } else { goog.asserts.assert( !Array.isArray(value), 'Value for non-repeated field ' + field + ' must not be an array.'); message.set(field, this.getDeserializedValue(field, value)); } } else { if (isNumeric) { // We have an unknown field (with a numeric tag). message.setUnknown(Number(key), value); } else { // Handle unknown non-numeric tag. if (!this.ignoreUnknownFields_) { // Named fields must be present. goog.asserts.fail('Failed to find field: ' + key); } } } } };