text: // Copyright 2008 The Closure Library Authors. All Rights Reserved.
  //
  // 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.
  
  /**
   * @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');
  
  
  
  /**
   * 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) {
    this.keyOption_ = opt_keyOption;
    this.serializeBooleanAsNumber_ = opt_serializeBooleanAsNumber;
    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) {
    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) { objectValue[tag] = value; });
  
    return objectValue;
  };
  
  
  /** @override */
  goog.proto2.ObjectSerializer.prototype.getSerializedValue = function(
      field, value) {
  
    // 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) {
  
    // 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) {
    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(
              goog.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(
              !goog.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);
          }
        }
      }
    }
  };
  