NOTA: Este sitio es obsoleto. El sitio se desactivará después del 31 de enero de 2023, y el tráfico se redireccionará al nuevo sitio disponible en https://protobuf.dev. Mientras tanto, solo se implementarán actualizaciones en protobuf.dev.

Guía de idiomas

Organiza tus páginas con colecciones Guarda y categoriza el contenido según tus preferencias.

En esta guía, se describe cómo usar el lenguaje del búfer de protocolo para estructurar los datos del búfer de protocolo, incluida la sintaxis del archivo .proto y cómo generar clases de acceso a los datos a partir de los archivos .proto. Abarca la versión proto2 del lenguaje del búfer de protocolo: para obtener información sobre la sintaxis de proto3, consulta la Guía del lenguaje Proto3.

Esta es una guía de referencia. Para ver un ejemplo paso a paso que usa muchas de las funciones descritas en este documento, consulta el instructivo del lenguaje que elegiste.

Definición de un tipo de mensaje

Primero veamos un ejemplo muy simple. Supongamos que deseas definir un formato de mensaje de solicitud de búsqueda, en el que cada solicitud de búsqueda tiene una string de consulta, la página en particular de los resultados que te interesan y una cantidad de resultados por página. Este es el archivo .proto que usas para definir el tipo de mensaje.


message SearchRequest {
  required string query = 1;
  optional int32 page_number = 2;
  optional int32 result_per_page = 3;
}

La definición del mensaje SearchRequest especifica tres campos (pares nombre/valor), uno para cada dato que deseas incluir en este tipo de mensaje. Cada campo tiene un nombre y un tipo.

Especifica los tipos de campo

En el ejemplo anterior, todos los campos son tipos escalares: dos números enteros (page_number y result_per_page) y una string (query). Sin embargo, también puedes especificar tipos compuestos para tus campos, incluidas enumeraciones y otros tipos de mensajes.

Asignación de números de campo

Como puedes ver, cada campo en la definición del mensaje tiene un número único. Estos números se usan para identificar tus campos en el formato binario de mensaje y no se deben cambiar una vez que el tipo de mensaje está en uso. Los números de campo en el rango 1 a 15 toman un byte a codificar, que incluye el número de campo y el tipo de campo (puedes encontrar más información sobre esto en Codificación de búfer de protocolo). Los números de campo en el rango del 16 al 2047 toman dos bytes. Por lo tanto, debes reservar los números de campo del 1 al 15 para los elementos de mensaje muy frecuentes. Recuerda dejar espacio para los elementos frecuentes que podrían agregarse en el futuro.

El número de campo más pequeño que puedes especificar es 1 y el más grande es 229 - 1 o 536,870,911. Tampoco puedes usar los números del 19 000 al 19999 (de FieldDescriptor::kFirstReservedNumber a FieldDescriptor::kLastReservedNumber), ya que están reservados para la implementación del búfer de protocolo. El compilador del búfer de protocolo se quejará si usas uno de estos números reservados en tu .proto. Del mismo modo, no puedes usar números de campo reservados anteriormente.

Especificación de reglas de campo

Especifica que los campos de mensaje son uno de los siguientes:

  • required: Un mensaje bien formado debe tener exactamente uno de este campo.
  • optional: un mensaje con formato correcto puede tener cero o uno de este campo (pero no más de uno).
  • repeated: Este campo se puede repetir cualquier cantidad de veces (incluido cero) en un mensaje con formato correcto. Se conservará el orden de los valores repetidos.

Por razones históricas, los campos repeated de tipos numéricos escalares (por ejemplo, int32, int64, enum) no se codifican de manera tan eficiente como podrían. El código nuevo debe usar la opción especial [packed = true] para obtener una codificación más eficiente. Por ejemplo:

repeated int32 samples = 4 [packed = true];
repeated ProtoEnum results = 5 [packed = true];

Puedes obtener más información sobre la codificación packed en Codificación de búfer de protocolo.

Obligatorio es para siempre Debes tener mucho cuidado cuando marcas campos como required. Si en algún momento quieres dejar de escribir o enviar un campo obligatorio, será difícil cambiar el campo a un campo opcional. Los lectores antiguos considerarán que los mensajes sin este campo están incompletos y pueden rechazarlos o descartarlos de manera no intencional. En su lugar, considera escribir rutinas de validación personalizadas específicas de la aplicación para los búferes.

Cuando alguien agrega un valor a la enumeración, aparece un segundo problema con los campos obligatorios. En este caso, el valor enum no reconocido se trata como si faltara, lo que también causa que la verificación de valor requerida falle.

Agregar más tipos de mensajes

Se pueden definir varios tipos de mensajes en un solo archivo .proto. Esto es útil si quieres definir varios mensajes relacionados; por ejemplo, si deseas definir el formato del mensaje de respuesta que corresponde a tu tipo de mensaje SearchResponse, puedes agregarlo al mismo .proto:


message SearchRequest {
  required string query = 1;
  optional int32 page_number = 2;
  optional int32 result_per_page = 3;
}

message SearchResponse {
 ...
}

Combinar mensajes genera un sobredimensionamiento Si bien se pueden definir varios tipos de mensajes (como mensajes, enumeraciones y servicios) en un solo archivo .proto, también se pueden generar aumentos excesivos de dependencias cuando se definen grandes cantidades de mensajes con distintas dependencias en un solo archivo. Se recomienda incluir la menor cantidad posible de tipos de mensajes por archivo .proto.

Agregar comentarios

Para agregar comentarios a tus archivos .proto, usa la sintaxis de estilo C/C++ // y /* ... */.


/* SearchRequest represents a search query, with pagination options to
 * indicate which results to include in the response. */

message SearchRequest {
  required string query = 1;
  optional int32 page_number = 2;  // Which page number do we want?
  optional int32 result_per_page = 3;  // Number of results to return per page.
}

Campos reservados

Si actualizas un tipo de mensaje mediante la eliminación completa de un campo, o el envío de comentarios, los usuarios futuros pueden volver a usar el número de campo cuando realicen sus propias actualizaciones en el tipo. Esto puede causar problemas graves si más adelante se cargan versiones anteriores del mismo .proto, por ejemplo, daños en los datos, errores de privacidad, etcétera. Una forma de asegurarte de que esto no suceda es especificar que los números de campo (o los nombres, que también pueden causar problemas para la serialización JSON) de tus campos borrados, sean reserved. El compilador del búfer de protocolo se quejará si algún usuario futuro intenta usar estos identificadores de campo.

message Foo {
  reserved 2, 15, 9 to 11;
  reserved "foo", "bar";
}

Los rangos de números de campo reservados son inclusivos (9 to 11 es igual a 9, 10, 11). Ten en cuenta que no puedes combinar nombres de campos y números de campo en la misma declaración reserved.

¿Qué se genera a partir de su .proto?

Cuando ejecutas el compilador de búfer de protocolo en una .proto, el compilador genera el código en el lenguaje elegido en el que deberás trabajar con los tipos de mensajes que describes en el archivo, incluida la obtención y configuración de valores de campo, la serialización de tus mensajes en una transmisión de salida y el análisis de tus mensajes desde una transmisión de entrada.

  • Para C++, el compilador genera un archivo .h y .cc a partir de cada .proto, con una clase para cada tipo de mensaje descrito en tu archivo.
  • En el caso de Java, el compilador genera un archivo .java con una clase para cada tipo de mensaje, así como clases Builder especiales a fin de crear instancias de clase de mensaje.
  • Python es un poco diferente: el compilador de Python genera un módulo con un descriptor estático de cada tipo de mensaje en tu .proto, que luego se usa con una metaclase para crear la clase de acceso a los datos de Python necesaria en el entorno de ejecución.
  • En el caso de Go, el compilador genera un archivo .pb.go con un tipo para cada tipo de mensaje en tu archivo.

Puedes obtener más información sobre cómo usar las API para cada idioma con este instructivo. Para obtener aún más detalles sobre la API, consulta la referencia de la API relevante.

Tipos de valores escalares

Un campo de mensaje escalar puede tener uno de los siguientes tipos: la tabla muestra el tipo especificado en el archivo .proto y el tipo correspondiente en la clase generada de forma automática:

Tipo .proto Notas Tipo de C++ Tipo de Java Tipo de Python[2] Tipo de Go
double double double float *float64
float float float float *float32
int32 Usa codificación de longitud variable. Es ineficiente para codificar números negativos. Si es probable que tu campo tenga valores negativos, usa sint32. int32 int int *int32
int64 Usa codificación de longitud variable. No es eficiente para codificar números negativos. Si es probable que tu campo tenga valores negativos, usa sint64 en su lugar. int64 long int/long[3] *int64
uint32 Usa codificación de longitud variable. uint32 número entero[1] int/long[3] *intinta32
uint64 Usa codificación de longitud variable. uint64 long[1] int/long[3] *uint64
sint32 Usa codificación de longitud variable. Valor int firmado. Codifican de manera más eficaz números negativos que números int32 normales. int32 int int *int32
SinT64 Usa codificación de longitud variable. Valor int firmado. Codifican de manera más eficaz los números negativos que los números int64 normales. int64 long int/long[3] *int64
Fijo32 Siempre cuatro bytes. Es más eficiente que uint32 si los valores suelen ser mayores que 228. uint32 número entero[1] int/long[3] *intinta32
fijo64 Siempre ocho bytes. Es más eficiente que uint64 si los valores suelen ser mayores que 256. uint64 long[1] int/long[3] *uint64
fijo32 Siempre cuatro bytes. int32 int int *int32
fijas64 Siempre ocho bytes. int64 long int/long[3] *int64
bool bool boolean bool *Booleano
string Una string siempre debe contener texto codificado en UTF-8. string String Unicode (Python 2) o str (Python 3) *string
bytes Puede contener cualquier secuencia arbitraria de bytes. string ByteString bytes []byte

Puedes obtener más información sobre cómo se codifican estos tipos cuando serializas tu mensaje en Codificación de búfer de protocolo.

[1] En Java, los números enteros de 32 bits y 64 bits sin firma se representan con sus equivalentes firmados, y el bit superior solo se almacena en el bit de signo.

[2] En todos los casos, la configuración de los valores de un campo realizará una verificación de tipo para asegurar que sea válida.

[3] Los números enteros de 62 bits o 32 bits sin firma se representan siempre como largos cuando se decodifican, pero pueden ser un número entero si se proporciona un número entero cuando se configura el campo. En todos los casos, el valor debe ajustarse al tipo representado cuando se configura. Consulta [2].

Campos opcionales y valores predeterminados

Como se mencionó anteriormente, los elementos de una descripción se pueden etiquetar como optional. Un mensaje bien formado puede o no contener un elemento opcional. Cuando se analiza un mensaje, si no contiene un elemento opcional, el acceso al campo correspondiente en el objeto analizado muestra el valor predeterminado para ese campo. El valor predeterminado se puede especificar como parte de la descripción del mensaje. Por ejemplo, supongamos que deseas proporcionar un valor predeterminado de 10 para el valor result_per_page de un SearchRequest.

optional int32 result_per_page = 3 [default = 10];

Si no se especifica el valor predeterminado para un elemento opcional, en su lugar se usa un valor predeterminado específico del tipo: para las strings, el valor predeterminado es la string vacía. Para los bytes, el valor predeterminado es la string de bytes vacía. Para booleanos, el valor predeterminado es falso. Para los tipos numéricos, el valor predeterminado es cero. En el caso de las enumeraciones, el valor predeterminado es el primero que aparece en la definición de tipo de la enumeración. Esto significa que se debe tener cuidado al agregar un valor al comienzo de una lista de valores de enumeración. Consulta la sección Actualiza un tipo de mensaje para conocer los lineamientos sobre cómo cambiar las definiciones de forma segura.

Enumeraciones

Cuando defines un tipo de mensaje, es posible que desees que uno de sus campos solo tenga una de una lista de valores predefinida. Por ejemplo, supongamos que deseas agregar un campo corpus para cada SearchRequest, donde el corpus puede ser UNIVERSAL, WEB, IMAGES, LOCAL, NEWS, PRODUCTS o VIDEO. Puedes hacerlo de manera muy simple si agregas un enum a tu definición de mensajes: un campo con un tipo enum solo puede tener uno de un conjunto específico de constantes como su valor (si intentas proporcionar un valor diferente, el analizador lo tratará como un campo desconocido). En el siguiente ejemplo, agregamos un enum llamado Corpus con todos los valores posibles y un campo de tipo Corpus:

enum Corpus {
  CORPUS_UNSPECIFIED = 0;
  CORPUS_UNIVERSAL = 1;
  CORPUS_WEB = 2;
  CORPUS_IMAGES = 3;
  CORPUS_LOCAL = 4;
  CORPUS_NEWS = 5;
  CORPUS_PRODUCTS = 6;
  CORPUS_VIDEO = 7;
}

message SearchRequest {
  required string query = 1;
  optional int32 page_number = 2;
  optional int32 result_per_page = 3 [default = 10];
  optional Corpus corpus = 4 [default = CORPUS_UNIVERSAL];
}

Puedes definir alias si asignas el mismo valor a diferentes constantes de enumeración. Para ello, debes establecer la opción allow_alias en true. De lo contrario, el compilador del búfer de protocolo generará un mensaje de error cuando se encuentren alias. Aunque todos los valores de alias son válidos durante la deserialización, el primer valor siempre se usa cuando se serializa.

enum EnumAllowingAlias {
  option allow_alias = true;
  EAA_UNSPECIFIED = 0;
  EAA_STARTED = 1;
  EAA_RUNNING = 1;
  EAA_FINISHED = 2;
}
enum EnumNotAllowingAlias {
  ENAA_UNSPECIFIED = 0;
  ENAA_STARTED = 1;
  // ENAA_RUNNING = 1;  // Uncommenting this line will cause a compile error inside Google and a warning message outside.
  ENAA_FINISHED = 2;
}

Las constantes del enumerador deben estar en el rango de un número entero de 32 bits. Dado que los valores enum usan codificación varint en la conexión, los valores negativos son ineficientes y, por lo tanto, no se recomiendan. Puedes definir enum en una definición de mensajes o fuera de ella. Estos enum se pueden volver a usar en cualquier definición de mensajes en tu archivo .proto. También puedes usar un tipo enum declarado en un mensaje como el tipo de un campo en un mensaje diferente, mediante la sintaxis _MessageType_._EnumType_.

Cuando ejecutas el compilador del búfer de protocolo en una .proto que usa un enum, el código generado tendrá una enum correspondiente para Java o C++, o una clase especial EnumDescriptor para Python que se usa con el fin de crear un conjunto de constantes simbólicas con valores de número entero en la clase generada en tiempo de ejecución.

Para obtener más información sobre cómo trabajar con los enum de mensajes en tus aplicaciones, consulta la guía de código generado para el lenguaje que elegiste.

Valores reservados

Si actualizas un tipo de enumeración cuando quitas una entrada de enumeración o la comentas por completo, los usuarios futuros pueden volver a usar el valor numérico cuando realicen sus propias actualizaciones en el tipo. Esto puede causar problemas graves si más adelante se cargan versiones anteriores del mismo .proto, por ejemplo, daños en los datos, errores de privacidad, etcétera. Una forma de asegurarte de que esto no suceda es especificar que los valores numéricos (o nombres, que también pueden causar problemas para la serialización JSON) de las entradas borradas son reserved. El compilador del búfer de protocolo se quejará si algún usuario futuro intenta usar estos identificadores. Puedes especificar que tu rango de valores numéricos reservado suba hasta el valor máximo posible mediante la palabra clave max.


enum Foo {
  reserved 2, 15, 9 to 11, 40 to max;
  reserved "FOO", "BAR";
}

Ten en cuenta que no puedes combinar nombres de campos y valores numéricos en la misma declaración reserved.

Cómo usar otros tipos de mensajes

Puedes usar otros tipos de mensajes como tipos de campo. Por ejemplo, supongamos que deseas incluir mensajes Result en cada mensaje SearchResponse. Para ello, puedes definir un tipo de mensaje Result en el mismo .proto y, luego, especificar un campo de tipo Result en SearchResponse:


message SearchResponse {
  repeated Result result = 1;
}

message Result {
  required string url = 1;
  optional string title = 2;
  repeated string snippets = 3;
}

Importar definiciones

En el ejemplo anterior, el tipo de mensaje Result se define en el mismo archivo que SearchResponse. ¿Qué sucede si el tipo de mensaje que deseas usar como tipo de campo ya está definido en otro archivo .proto?

Puedes usar definiciones de otros archivos .proto si los importas. Para importar las definiciones de .proto, agrega una instrucción de importación en la parte superior del archivo:

import "myproject/other_protos.proto";

De forma predeterminada, solo puedes usar definiciones de archivos .proto importados directamente. Sin embargo, a veces es posible que debas mover un archivo .proto a una ubicación nueva. En lugar de mover el archivo .proto directamente y actualizar todos los sitios de llamadas en un solo cambio, puedes colocar un archivo de marcador de posición .proto en la ubicación anterior para reenviar todas las importaciones a la ubicación nueva mediante la noción import public.

Ten en cuenta que la función de importación pública no está disponible en Java.

Las dependencias import public pueden depender de forma transitiva en cualquier código que importe el proto que contenga la declaración import public. Por ejemplo:

// new.proto
// All definitions are moved here
// old.proto
// This is the proto that all clients are importing.
import public "new.proto";
import "other.proto";
// client.proto
import "old.proto";
// You use definitions from old.proto and new.proto, but not other.proto

El compilador de protocolos busca archivos importados en un conjunto de directorios especificados en la línea de comandos del compilador del protocolo mediante la marca -I/--proto_path. Si no se proporcionó ninguna marca, se ve en el directorio en el que se invocó el compilador. En general, debes establecer la marca --proto_path en la raíz de tu proyecto y usar nombres completos para todas las importaciones.

Usa tipos de mensajes proto3

Es posible importar tipos de mensajes proto3 y usarlos en tus mensajes proto2, y viceversa. Sin embargo, las enumeraciones proto2 no se pueden usar en la sintaxis proto3.

Tipos anidados

Puedes definir y usar tipos de mensajes dentro de otros tipos de mensajes, como en el siguiente ejemplo: aquí el mensaje Result se define dentro del mensaje SearchResponse:

message SearchResponse {
  message Result {
    required string url = 1;
    optional string title = 2;
    repeated string snippets = 3;
  }
  repeated Result result = 1;
}

Si deseas volver a usar este tipo de mensaje fuera de su tipo de mensaje superior, debes consultarlo como _Parent_._Type_:

message SomeOtherMessage {
  optional SearchResponse.Result result = 1;
}

Puedes anidar mensajes tan profundamente como quieras. En el siguiente ejemplo, ten en cuenta que los dos tipos anidados llamados Inner son completamente independientes, ya que se definen dentro de mensajes diferentes:

message Outer {       // Level 0
  message MiddleAA {  // Level 1
    message Inner {   // Level 2
      optional int64 ival = 1;
      optional bool  booly = 2;
    }
  }
  message MiddleBB {  // Level 1
    message Inner {   // Level 2
      optional string name = 1;
      optional bool   flag = 2;
    }
  }
}

Grupos

Ten en cuenta que la función de grupos dejó de estar disponible y no debes usarla cuando crees tipos de mensajes nuevos. En su lugar, usa tipos de mensajes anidados.

Los grupos son otra forma de anidar información en las definiciones de mensajes. Por ejemplo, otra forma de especificar un SearchResponse que contiene una cantidad de Result es la siguiente:

message SearchResponse {
  repeated group Result = 1 {
    required string url = 2;
    optional string title = 3;
    repeated string snippets = 4;
  }
}

Un grupo simplemente combina un tipo de mensaje anidado y un campo en una sola declaración. En tu código, puedes tratar este mensaje como si tuviera un campo de tipo Result llamado result (el último nombre se convierte en minúsculas para que no entre en conflicto con el primero). Por lo tanto, este ejemplo es exactamente equivalente a SearchResponse anterior, excepto que el mensaje tiene un formato de conexión diferente.

Cómo actualizar un tipo de mensaje

Si un tipo de mensaje existente ya no satisface todas tus necesidades (por ejemplo, te gustaría que el formato de mensaje tenga un campo adicional), pero de todos modos quieres usar el código creado con el formato anterior, no te preocupes. Es muy simple actualizar los tipos de mensajes sin romper el código existente cuando usas el formato de cable binario.

Si usas el formato de cable binario, verifica las siguientes reglas:

  • No cambies los números de campo de los campos existentes.
  • Los campos nuevos que agregues deben ser optional o repeated. Esto significa que los mensajes serializados por código con tu formato de mensaje "anterior" se pueden analizar en función del nuevo código generado, ya que no faltarán elementos required. Debes establecer valores predeterminados razonables para estos elementos a fin de que el código nuevo pueda interactuar de forma adecuada con los mensajes generados por el código antiguo. Del mismo modo, los mensajes creados por el código nuevo se pueden analizar con el código anterior: los objetos binarios antiguos simplemente ignoran el campo nuevo cuando se realiza un análisis. Sin embargo, los campos desconocidos no se descartan y, si el mensaje se serializa en otro momento, los campos desconocidos se serializan con él, por lo que, si el mensaje se pasa a un código nuevo, los campos nuevos todavía están disponibles.
  • Se pueden quitar los campos obligatorios, siempre y cuando el número de campo no se vuelva a usar en el tipo de mensaje actualizado. Es posible que quieras renombrar el campo como agregar el prefijo "OBSOLETE_", o hacer que el número de campo esté reservado para que los usuarios futuros de tu .proto no puedan volver a usar el número por accidente.
  • Un campo no obligatorio se puede convertir en una extensión y viceversa, siempre que el tipo y el número sean los mismos.
  • int32, uint32, int64, uint64 y bool son compatibles, lo que significa que puedes cambiar un campo de uno de estos tipos a otro sin tener que avanzar y volver a la compatibilidad con versiones anteriores. Si se analiza un número desde el cable que no cabe en el tipo correspondiente, obtendrás el mismo efecto que si convirtieras el número a ese tipo en C++ (por ejemplo, si un número de 64 bits se lee como un int32, se truncará a 32 bits).
  • sint32 y sint64 son compatibles entre sí, pero no son compatibles con los otros tipos de números enteros.
  • string y bytes son compatibles siempre que los bytes sean UTF-8 válidos.
  • Los mensajes incorporados son compatibles con bytes si los bytes contienen una versión codificada del mensaje.
  • fixed32 es compatible con sfixed32 y fixed64 con sfixed64.
  • Para string, bytes y los campos de mensaje, optional es compatible con repeated. Dados los datos serializados de un campo repetido como entrada, los clientes que esperan que este campo sea optional tomarán el último valor de entrada si es un campo de tipo primitivo o combinarán todos los elementos de entrada si es un campo de tipo de mensaje. Ten en cuenta que no es generalmente seguro para los tipos numéricos, incluidos los booleanos y las enumeraciones. Los campos repetidos de tipos numéricos se pueden serializar en el formato empaquetado, que no se analizarán correctamente cuando se espere un campo optional.
  • Cambiar un valor predeterminado suele ser aceptable, siempre y cuando recuerdes que los valores predeterminados nunca se envían por cable. Por lo tanto, si un programa recibe un mensaje en el que no se configuró un campo en particular, el programa verá el valor predeterminado como se definió en la versión del protocolo de ese programa. NO verá el valor predeterminado que se definió en el código del remitente.
  • enum es compatible con int32, uint32, int64 y uint64 en términos de formato de conexión (ten en cuenta que los valores se truncarán si no encajan). Sin embargo, ten en cuenta que el código del cliente puede tratarlos de manera diferente cuando se deserializa el mensaje. En particular, los valores enum no reconocidos se descartan cuando se deserializa el mensaje, lo que hace que el descriptor de acceso del campo has.. muestre falso y su captador muestre el primer valor que aparece en la definición enum, o el valor predeterminado si se especifica uno. En el caso de los campos de enumeración repetidas, los valores no reconocidos se quitan de la lista. Sin embargo, un campo de número entero siempre conservará su valor. Debido a esto, debes tener mucho cuidado cuando actualices un número entero a un enum en términos de recibir valores de enumeración fuera de los límites en el cable.
  • En las implementaciones actuales de Java y C++, cuando se quitan los valores enum no reconocidos, se almacenan junto con otros campos desconocidos. Ten en cuenta que el resultado puede ser un comportamiento extraño si los datos se serializan y, luego, se vuelven a analizar por un cliente que reconoce estos valores. En el caso de los campos opcionales, incluso si se escribió un valor nuevo después de deserializar el mensaje original, los clientes que aún lo reconocen podrán leer el valor anterior. En el caso de los campos repetidos, los valores anteriores aparecerán después de los valores reconocidos y agregados recientemente, lo que significa que no se conservará el orden.
  • Cambiar una sola extensión o campo optional por un miembro de una oneof nueva es compatible con objetos binarios; sin embargo, para algunos lenguajes (en particular, Go), la API del código generado cambiará de formas incompatibles. Por este motivo, Google no realiza estos cambios en sus API públicas, como se documenta en AIP-180. Con la misma advertencia sobre la compatibilidad de origen, mover varios campos a un oneof nuevo puede ser seguro si estás seguro de que ningún código establece más de uno a la vez. Mover campos a un oneof existente no es seguro. Del mismo modo, cambiar un solo campo oneof por un campo optional o una extensión es seguro.
  • Cambiar un campo entre una map<K, V> y el campo de mensaje repeated correspondiente es compatible con objetos binarios (consulta la sección Maps que se encuentra a continuación para conocer el diseño del mensaje y otras restricciones). Sin embargo, la seguridad del cambio depende de la aplicación: cuando se deserializan y vuelven a serializar un mensaje, los clientes que usan la definición del campo repeated producirán un resultado semántico idéntico. Sin embargo, los clientes que usan la definición del campo map pueden volver a ordenar las entradas y descartar las entradas con claves duplicadas.

Extensiones

Las extensiones te permiten declarar que un rango de números de campo en un mensaje está disponible para extensiones de terceros. Una extensión es un marcador de posición para un campo cuyo tipo no está definido por el archivo .proto original. Esto permite que otros archivos .proto se agreguen a tu definición de mensajes mediante la definición de los tipos de algunos o todos los campos con esos números de campo. Veamos un ejemplo:

message Foo {
  // ...
  extensions 100 to 199;
}

Esto indica que el rango de números de campo [100, 199] en Foo está reservado para extensiones. Otros usuarios ahora pueden agregar campos nuevos a Foo en sus propios archivos .proto que importan tu .proto mediante números de campo dentro del rango especificado, por ejemplo:

extend Foo {
  optional int32 bar = 126;
}

Esto agrega un campo llamado bar con el campo número 126 a la definición original de Foo.

Cuando los mensajes Foo de tu usuario están codificados, el formato de conexión es exactamente el mismo que si el usuario definiera el campo nuevo dentro de Foo. Sin embargo, la forma en que accedes a los campos de extensiones en el código de tu aplicación es un poco diferente a acceder a los campos normales. Tu código de acceso a los datos generados tiene descriptores de acceso especiales para trabajar con extensiones. Por ejemplo, a continuación, se muestra cómo establecer el valor de bar en C++:

Foo foo;
foo.SetExtension(bar, 15);

De manera similar, la clase Foo define los descriptores de acceso de plantilla HasExtension(), ClearExtension(), GetExtension(), MutableExtension() y AddExtension(). Todas tienen semántica que coincide con los descriptores de acceso correspondientes para un campo normal. Para obtener más información sobre cómo trabajar con extensiones, consulta la referencia del código generado para el idioma elegido.

Ten en cuenta que las extensiones pueden ser de cualquier tipo de campo, incluidos los tipos de mensajes, pero no pueden ser únicos ni mapas.

Extensiones anidadas

Puedes declarar extensiones en el alcance de otro tipo:

message Baz {
  extend Foo {
    optional int32 bar = 126;
  }
  ...
}

En este caso, el código de C++ para acceder a esta extensión es el siguiente:

Foo foo;
foo.SetExtension(Baz::bar, 15);

En otras palabras, el único efecto es que bar se define dentro del alcance de Baz.

Esta es una fuente común de confusión: declarar un bloque extend anidado dentro de un tipo de mensaje no implica ninguna relación entre el tipo externo y el extendido. En particular, el ejemplo anterior no significa que Baz sea algún tipo de subclase de Foo. Todo lo que significa es que el símbolo bar se declara dentro del alcance de Baz; es simplemente un miembro estático.

Un patrón común es definir extensiones dentro del alcance del tipo de campo de la extensión; por ejemplo, esta es una extensión a Foo de tipo Baz, en la que la extensión se define como parte de Baz:

message Baz {
  extend Foo {
    optional Baz foo_ext = 127;
  }
  ...
}

Sin embargo, no es necesario que una extensión con un tipo de mensaje se defina dentro de ese tipo. También puedes hacer lo siguiente:

message Baz {
  ...
}

// This can even be in a different file.
extend Foo {
  optional Baz foo_baz_ext = 127;
}

De hecho, es posible que se prefiera usar esta sintaxis para evitar confusiones. Como se mencionó anteriormente, la sintaxis anidada suele confundirse con la subclasificación de los usuarios que aún no están familiarizados con las extensiones.

Cómo elegir los números de extensión

Es muy importante asegurarse de que dos usuarios no agreguen extensiones al mismo tipo de mensaje con el mismo número de campo. El daño a los datos puede producirse si una extensión se interpreta como un tipo incorrecto. Te recomendamos que definas una convención de numeración de extensiones para tu proyecto a fin de evitar que eso suceda.

Si tu convención de numeración podría implicar que las extensiones tengan números de campo muy grandes, puedes usar la palabra clave max para especificar que tu rango de extensión vaya al número máximo de campos posible:

message Foo {
  extensions 1000 to max;
}

max es 229: 1 o 536,870,911.

Al igual que cuando eliges números de campo en general, la convención de numeración también debe evitar los campos de 19000 a 19999 (de FieldDescriptor::kFirstReservedNumber a FieldDescriptor::kLastReservedNumber), ya que están reservados para la implementación del búfer de protocolo. Puedes definir un rango de extensiones que incluya este rango, pero el compilador del protocolo no te permitirá definir extensiones reales con estos números.

Uno de

Si tienes un mensaje con muchos campos opcionales y en los que se configurará al menos un campo al mismo tiempo, puedes aplicar este comportamiento y ahorrar memoria con la función oneof.

Uno de los campos es como uno opcional, excepto todos los campos de una memoria compartida, y como máximo se puede configurar un campo al mismo tiempo. La configuración de cualquier miembro de uno de ellos borra automáticamente todos los demás miembros. Puedes verificar qué valor de un conjunto se configura (si existe) con un método especial case() o WhichOneof(), según el idioma elegido.

Usa Oneof

Para definir uno en tu .proto, usa la palabra clave oneof seguida de tu nombre, en este caso test_oneof:

message SampleMessage {
  oneof test_oneof {
     string name = 4;
     SubMessage sub_message = 9;
  }
}

Luego, debe agregar sus campos oneof a la definición oneof. Puedes agregar campos de cualquier tipo, pero no puedes usar las palabras clave required, optional o repeated. Si necesitas agregar un campo repetido a uno de ellos, puedes usar un mensaje que contenga el campo repetido.

En el código generado, uno de los campos tiene los mismos métodos get y set que los métodos optional normales. También obtienes un método especial para verificar qué valor está configurado en uno de ellos. Puedes obtener más información sobre la API del idioma seleccionado en la referencia de la API relevante.

Una de las características

  • Si se configura un campo "oneof", se borrarán automáticamente todos los demás miembros de este. Por lo tanto, si configuras varios campos, solo el último campo que establezcas tendrá un valor.

    SampleMessage message;
    message.set_name("name");
    CHECK(message.has_name());
    message.mutable_sub_message();   // Will clear name field.
    CHECK(!message.has_name());
    
  • Si el analizador encuentra varios miembros del mismo cable en el cable, solo se usa el último que se vio en el mensaje analizado.

  • Las extensiones no son compatibles con uno de ellos.

  • Uno de ellos no puede ser repeated.

  • Las API de reflexión funcionan para uno de los campos.

  • Si configuras un campo oneof con el valor predeterminado (como establecer un campo int32 oneof en 0), se establecerá el “caso” de ese campo one y el valor se serializará en el cable.

  • Si usas C++, asegúrate de que tu código no genere fallas en la memoria. El siguiente código de muestra fallará porque sub_message ya se borró mediante una llamada al método set_name().

    SampleMessage message;
    SubMessage* sub_message = message.mutable_sub_message();
    message.set_name("name");      // Will delete sub_message
    sub_message->set_...            // Crashes here
    
  • Nuevamente en C++, si Swap() dos mensajes con uno de ellos, cada mensaje terminará con el caso del otro: en el siguiente ejemplo, msg1 tendrá un sub_message y msg2 tendrá un name.

    SampleMessage msg1;
    msg1.set_name("name");
    SampleMessage msg2;
    msg2.mutable_sub_message();
    msg1.swap(&msg2);
    CHECK(msg1.has_sub_message());
    CHECK(msg2.has_name());
    

Problemas de retrocompatibilidad

Ten cuidado cuando agregues o quites uno de los campos. Si, cuando se verifica el valor de uno de estos, se muestra None/NOT_SET, podría significar que el oneof no se configuró o se configuró en un campo en una versión diferente del oneof. No hay forma de saber la diferencia, ya que no hay forma de saber si un campo desconocido en el cable es miembro de uno de ellos.

Problemas de reutilización de etiquetas

  • Mover campos opcionales a uno de ellos o quitarlos de ellos: Es posible que pierdas parte de la información (algunos campos se borrarán) después de que el mensaje se serializa y analiza. Sin embargo, puedes mover de forma segura un solo campo a uno nuevo y quizás puedas mover varios campos si se sabe que solo se estableció uno. Consulta Actualiza un tipo de mensaje para obtener más detalles.
  • Borra un campo One y vuelve a agregarlo: Es posible que se borre el campo oneof establecido actualmente después de que el mensaje se serializa y se analiza.
  • Dividir o combinar uno: Esto tiene problemas similares a mover campos optional regulares.

Maps

Si deseas crear un mapa asociativo como parte de tu definición de datos, los búferes de protocolo proporcionan una sintaxis de acceso directo práctica:

map<key_type, value_type> map_field = N;

...en el que key_type puede ser cualquier tipo integral o de string (así que cualquier tipo escalar, excepto los tipos de punto flotante y bytes). Ten en cuenta que enum no es un key_type válido. value_type puede ser de cualquier tipo, excepto otro mapa.

Entonces, por ejemplo, si deseas crear un mapa de proyectos en el que cada mensaje Project esté asociado con una clave de string, podrías definirlo de la siguiente manera:

map<string, Project> projects = 3;

Por el momento, la API de mapas generada se encuentra disponible para todos los idiomas compatibles con proto2. Puedes obtener más información sobre la API de Google Maps para el idioma elegido en la referencia de la API relevante.

Funciones de Maps

  • Las extensiones no son compatibles con los mapas.
  • Los mapas no pueden ser repeated, optional ni required.
  • El orden de los cables y la iteración de mapas correspondientes a los valores de mapa no están definidos, por lo que no puedes confiar en que los elementos de tu mapa estén en un orden específico.
  • Cuando se genera el formato de texto para una .proto, los mapas se ordenan por clave. Las claves numéricas se ordenan de forma numérica.
  • Durante el análisis desde la transferencia o la combinación, si se usan claves de mapa duplicadas, se utilizará la última clave que se vio. Cuando se analiza un mapa a partir de un formato de texto, el análisis puede fallar si hay claves duplicadas.

Compatibilidad con versiones anteriores

La sintaxis del mapa es equivalente a la siguiente en el cable, por lo que las implementaciones de búferes de protocolo que no admiten mapas aún pueden manejar tus datos:

message MapFieldEntry {
  optional key_type key = 1;
  optional value_type value = 2;
}

repeated MapFieldEntry map_field = N;

Cualquier implementación de búfer de protocolo que admite mapas debe producir y aceptar datos que la definición anterior acepte.

Paquetes

Puedes agregar un especificador package opcional a un archivo .proto para evitar conflictos de nombres entre los tipos de mensajes de protocolo.

package foo.bar;
message Open { ... }

Luego, puedes usar el especificador de paquetes cuando definas campos de tu tipo de mensaje:

message Foo {
  ...
  required foo.bar.Open open = 1;
  ...
}

La forma en que un especificador de paquetes afecta el código generado depende del lenguaje que hayas elegido:

  • En C++, las clases generadas se unen en un espacio de nombres de C++. Por ejemplo, Open estaría en el espacio de nombres foo::bar.
  • En Java, el paquete se usa como el paquete de Java, a menos que proporciones explícitamente un option java_package en tu archivo .proto.
  • En Python, se ignora la directiva package, ya que los módulos de Python se organizan según su ubicación en el sistema de archivos.
  • En Go, se ignora la directiva package y el archivo .pb.go generado se encuentra en el paquete que lleva el nombre de la regla go_proto_library correspondiente.

Ten en cuenta que, incluso cuando la directiva package no afecta directamente el código generado, por ejemplo, en Python, se recomienda especificar el paquete del archivo .proto, ya que, de lo contrario, podría generar conflictos en los descriptores y hacer que el protocolo no sea portátil para otros lenguajes.

Paquetes y resolución de nombres

La resolución de nombres de tipo en el lenguaje de búfer de protocolo funciona como C++: primero se busca el alcance más interno, luego el siguiente interno, y así sucesivamente, con cada paquete que se considera "interno" para su paquete superior. Un “.” inicial (por ejemplo, .foo.bar.Baz) significa comenzar desde el alcance más externo.

El compilador del búfer de protocolo resuelve todos los nombres de tipos mediante el análisis de los archivos .proto importados. El generador de códigos para cada idioma sabe cómo referirse a cada tipo en ese idioma, incluso si tiene diferentes reglas de alcance.

Definición de servicios

Si deseas usar tus tipos de mensajes con un sistema de RPC (llamada de procedimiento remoto), puedes definir una interfaz de servicio de RPC en un archivo .proto, y el compilador del búfer de protocolo generará el código de la interfaz de servicio y los stubs en el lenguaje que hayas elegido. Entonces, por ejemplo, si deseas definir un servicio de RPC con un método que toma tu SearchRequest y muestra un SearchResponse, puedes definirlo en tu archivo .proto de la siguiente manera:

service SearchService {
  rpc Search(SearchRequest) returns (SearchResponse);
}

De forma predeterminada, el compilador del protocolo generará una interfaz abstracta llamada SearchService y una implementación "stub" correspondiente. El stub reenvía todas las llamadas a un RpcChannel, que, a su vez, es una interfaz abstracta que debes definir en términos de tu propio sistema de RPC. Por ejemplo, puedes implementar un RpcChannel que serializa el mensaje y lo envía a un servidor a través de HTTP. En otras palabras, el stub generado proporciona una interfaz de tipo seguro para realizar llamadas RPC basadas en búferes de protocolo, sin bloquearte en ninguna implementación de RPC en particular. Por lo tanto, en C++, podrías obtener un código como el siguiente:

using google::protobuf;

protobuf::RpcChannel* channel;
protobuf::RpcController* controller;
SearchService* service;
SearchRequest request;
SearchResponse response;

void DoSearch() {
  // You provide classes MyRpcChannel and MyRpcController, which implement
  // the abstract interfaces protobuf::RpcChannel and protobuf::RpcController.
  channel = new MyRpcChannel("somehost.example.com:1234");
  controller = new MyRpcController;

  // The protocol compiler generates the SearchService class based on the
  // definition given above.
  service = new SearchService::Stub(channel);

  // Set up the request.
  request.set_query("protocol buffers");

  // Execute the RPC.
  service->Search(controller, &request, &response,
                  protobuf::NewCallback(&Done));
}

void Done() {
  delete service;
  delete channel;
  delete controller;
}

Todas las clases de servicio también implementan la interfaz Service, que proporciona una manera de llamar a métodos específicos sin conocer el nombre del método ni sus tipos de entrada y salida durante el tiempo de compilación. En el servidor, se puede usar para implementar un servidor RPC con el que puedas registrar servicios.

using google::protobuf;

class ExampleSearchService : public SearchService {
 public:
  void Search(protobuf::RpcController* controller,
              const SearchRequest* request,
              SearchResponse* response,
              protobuf::Closure* done) {
    if (request->query() == "google") {
      response->add_result()->set_url("http://www.google.com");
    } else if (request->query() == "protocol buffers") {
      response->add_result()->set_url("http://protobuf.googlecode.com");
    }
    done->Run();
  }
};

int main() {
  // You provide class MyRpcServer.  It does not have to implement any
  // particular interface; this is just an example.
  MyRpcServer server;

  protobuf::Service* service = new ExampleSearchService;
  server.ExportOnPort(1234, service);
  server.Run();

  delete service;
  return 0;
}

Si no quieres conectar tu propio sistema de RPC existente, ahora puedes usar gRPC, un sistema de RPC de código abierto independiente de la plataforma y el lenguaje desarrollado en Google. gRPC funciona particularmente bien con búferes de protocolo y te permite generar el código RPC relevante directamente desde tus archivos .proto con un complemento de compilador de búfer de protocolo especial. Sin embargo, como hay posibles problemas de compatibilidad entre clientes y servidores generados con proto2 y proto3, te recomendamos que uses proto3 para definir los servicios de gRPC. Puedes obtener más información sobre la sintaxis de proto3 en la guía del lenguaje Proto3. Si deseas usar proto2 con gRPC, debes usar la versión 3.0.0 o una versión posterior del compilador de búferes de protocolo y de las bibliotecas.

Además de gRPC, también hay una serie de proyectos de terceros en curso a fin de desarrollar implementaciones de RPC para los búferes de protocolo. Para obtener una lista de vínculos a proyectos que conocemos, consulta la página wiki de complementos de terceros.

Opciones

Las declaraciones individuales en un archivo .proto se pueden anotar con una serie de opciones. Las opciones no cambian el significado general de una declaración, pero pueden afectar la forma en que se maneja en un contexto particular. La lista completa de las opciones disponibles se define en /google/protobuf/descriptor.proto.

Algunas opciones son a nivel de archivo, lo que significa que se deben escribir en el nivel superior, no dentro de ningún mensaje, enumeración ni definición del servicio. Algunas opciones son opciones a nivel de mensaje, lo que significa que deben escribirse dentro de las definiciones de mensajes. Algunas son opciones a nivel de campo, lo que significa que deben escribirse dentro de las definiciones de campos. Las opciones también se pueden escribir en tipos de enumeración, valores de enumeración, uno de los campos, tipos de servicio y métodos de servicio. Sin embargo, por el momento no existen opciones útiles para ninguno de estos.

Estas son algunas de las opciones más usadas:

  • java_package (opción de archivo): Es el paquete que deseas usar para tus clases de Java generadas. Si no se proporciona ninguna opción explícita java_package en el archivo .proto, se usará el paquete de proto (especificado mediante la palabra clave "paquete" en el archivo .proto) de forma predeterminada. Sin embargo, por lo general, los paquetes proto no son buenos paquetes de Java, ya que no se espera que los paquetes proto comiencen con nombres de dominio inversos. Si no se genera código Java, esta opción no tiene efecto.

    option java_package = "com.example.foo";
    
  • java_outer_classname (opción de archivo): Es el nombre de clase (y, por lo tanto, el nombre de archivo) para la clase de Java de wrapper que deseas generar. Si no se especifica un java_outer_classname explícito en el archivo .proto, el nombre de la clase se construirá convirtiendo el nombre del archivo .proto en mayúsculas y minúsculas (de modo que foo_bar.proto se convierta en FooBar.java). Si la opción java_multiple_files está inhabilitada, todas las otras clases, enumeraciones, etc. generadas para el archivo .proto se generarán dentro de esta clase de wrapper externo de Java como clases, enumeraciones, etc. anidadas. Si no se genera código Java, esta opción no tiene efecto.

    option java_outer_classname = "Ponycopter";
    
  • java_multiple_files (opción de archivo): Si es falso, solo se generará un archivo .java para este archivo .proto, y todas las clases, enumeraciones, etc. de Java generadas para los mensajes de nivel superior, los servicios y las enumeraciones se anidarán dentro de una clase externa (consulta java_outer_classname). Si es verdadero, se generarán archivos .java separados para cada una de las clases, enumeraciones, clases, etc. generadas por los métodos, los archivos y los recursos Si no se genera código Java, esta opción no tiene efecto.

    option java_multiple_files = true;
    
  • optimize_for (opción de archivo): se puede configurar en SPEED, CODE_SIZE o LITE_RUNTIME. Esto afecta a los generadores de código C++ y Java (y, posiblemente, los de terceros) de las siguientes maneras:

    • SPEED (opción predeterminada): El compilador del búfer de protocolo generará código para serializar, analizar y realizar otras operaciones comunes en tus tipos de mensajes. Este código está altamente optimizado.
    • CODE_SIZE: El compilador del búfer de protocolo generará clases mínimas y se basará en el código compartido basado en reflexiones para implementar la serialización, el análisis y muchas otras operaciones. Por lo tanto, el código generado será mucho más pequeño que con SPEED, pero las operaciones serán más lentas. Las clases seguirán implementando exactamente la misma API pública que en el modo SPEED. Este modo es más útil en apps que contienen una gran cantidad de archivos .proto y no necesitan que todas sean rápidas para los usuarios.
    • LITE_RUNTIME: El compilador del búfer de protocolo generará clases que dependen solo de la biblioteca del entorno de ejecución "lite" (libprotobuf-lite en lugar de libprotobuf). El entorno de ejecución lite es mucho más pequeño que la biblioteca completa (alrededor de un orden de magnitud más pequeño), pero omite ciertas funciones como los descriptores y la reflexión. Esto es particularmente útil para las apps que se ejecutan en plataformas limitadas, como los teléfonos celulares. El compilador seguirá generando implementaciones rápidas de todos los métodos, como lo hace en el modo SPEED. Las clases generadas solo implementarán la interfaz MessageLite en cada lenguaje, lo que proporciona solo un subconjunto de los métodos de la interfaz Message completa.
    option optimize_for = CODE_SIZE;
    
  • cc_generic_services, java_generic_services, py_generic_services (opciones de archivo): Indica si el compilador de búfer de protocolo debe generar código abstracto de servicio según las definiciones de los servicios en C++, Java y Python, respectivamente. Por motivos heredados, el valor predeterminado es true. Sin embargo, a partir de la versión 2.3.0 (enero de 2010), se considera preferible a las implementaciones de RPC proporcionar complementos de generación de códigos a fin de generar un código más específico para cada sistema, en lugar de depender de los servicios “abstractos”.

    // This file relies on plugins to generate service code.
    option cc_generic_services = false;
    option java_generic_services = false;
    option py_generic_services = false;
    
  • cc_enable_arenas (opción de archivo): Habilita la asignación de Arena para el código generado de C++.

  • message_set_wire_format (opción de mensaje): si se configura como true, el mensaje usa un formato binario diferente destinado a ser compatible con un formato antiguo que se usa dentro de Google, llamado MessageSet. Es probable que los usuarios fuera de Google nunca necesiten usar esta opción. El mensaje debe declararse de la siguiente manera:

    message Foo {
      option message_set_wire_format = true;
      extensions 4 to max;
    }
    
  • packed (opción de campo): Si se configura como true en un campo repetido de un tipo numérico básico, se usa una codificación más compacta. Esta opción no tiene ningún inconveniente. Sin embargo, ten en cuenta que, antes de la versión 2.3.0, los analizadores que recibían datos empaquetados cuando no se esperaban los ignoraban. Por lo tanto, no era posible cambiar un campo existente al formato empaquetado sin romper la compatibilidad con los cables. En la versión 2.3.0 y posteriores, este cambio es seguro, ya que los analizadores de los campos empaquetables siempre aceptarán ambos formatos, pero ten cuidado si tienes que lidiar con los programas antiguos mediante versiones anteriores de protobuf.

    repeated int32 samples = 4 [packed = true];
    
  • deprecated (opción de campo): si se establece en true, indica que el campo está obsoleto y no debe usarse en código nuevo. En la mayoría de los lenguajes, esto no tiene efecto real. En Java, esto se convierte en una anotación @Deprecated. Para C++, clang-tidy generará advertencias cada vez que se usen campos obsoletos. En el futuro, es posible que otros generadores de código específicos de lenguaje generen anotaciones de baja en los descriptores de acceso del campo, lo que, a su vez, generará una advertencia cuando se compile el código que intenta usar el campo. Si nadie usa el campo y quieres evitar que lo usen nuevos usuarios, considera reemplazar la declaración de campo por una declaración reserved.

    optional int32 old_field = 6 [deprecated=true];
    

Opciones personalizadas

Los búferes de protocolo incluso te permiten definir y usar tus propias opciones. Ten en cuenta que esta es una función avanzada que la mayoría de las personas no necesitan. Dado que las opciones se definen mediante los mensajes definidos en google/protobuf/descriptor.proto (como FileOptions o FieldOptions), definir tus propias opciones es solo una cuestión de extender esos mensajes. Por ejemplo:

import "google/protobuf/descriptor.proto";

extend google.protobuf.MessageOptions {
  optional string my_option = 51234;
}

message MyMessage {
  option (my_option) = "Hello world!";
}

Aquí definimos una nueva opción a nivel de mensaje extendiendo MessageOptions. Cuando usamos la opción, el nombre de la opción debe encerrarse entre paréntesis para indicar que es una extensión. Ahora podemos leer el valor de my_option en C++ de la siguiente manera:

string value = MyMessage::descriptor()->options().GetExtension(my_option);

Aquí, MyMessage::descriptor()->options() muestra el mensaje de protocolo MessageOptions para MyMessage. Leer las opciones personalizadas desde allí es como leer cualquier otra extensión.

De manera similar, en Java escribiríamos:

String value = MyProtoFile.MyMessage.getDescriptor().getOptions()
  .getExtension(MyProtoFile.myOption);

En Python sería así:

value = my_proto_file_pb2.MyMessage.DESCRIPTOR.GetOptions()
  .Extensions[my_proto_file_pb2.my_option]

Se pueden definir opciones personalizadas para cada tipo de construcción en el lenguaje de los búferes de protocolo. Este es un ejemplo en el que se usa todo tipo de opción:

import "google/protobuf/descriptor.proto";

extend google.protobuf.FileOptions {
  optional string my_file_option = 50000;
}
extend google.protobuf.MessageOptions {
  optional int32 my_message_option = 50001;
}
extend google.protobuf.FieldOptions {
  optional float my_field_option = 50002;
}
extend google.protobuf.OneofOptions {
  optional int64 my_oneof_option = 50003;
}
extend google.protobuf.EnumOptions {
  optional bool my_enum_option = 50004;
}
extend google.protobuf.EnumValueOptions {
  optional uint32 my_enum_value_option = 50005;
}
extend google.protobuf.ServiceOptions {
  optional MyEnum my_service_option = 50006;
}
extend google.protobuf.MethodOptions {
  optional MyMessage my_method_option = 50007;
}

option (my_file_option) = "Hello world!";

message MyMessage {
  option (my_message_option) = 1234;

  optional int32 foo = 1 [(my_field_option) = 4.5];
  optional string bar = 2;
  oneof qux {
    option (my_oneof_option) = 42;

    string quux = 3;
  }
}

enum MyEnum {
  option (my_enum_option) = true;

  FOO = 1 [(my_enum_value_option) = 321];
  BAR = 2;
}

message RequestType {}
message ResponseType {}

service MyService {
  option (my_service_option) = FOO;

  rpc MyMethod(RequestType) returns(ResponseType) {
    // Note:  my_method_option has type MyMessage.  We can set each field
    //   within it using a separate "option" line.
    option (my_method_option).foo = 567;
    option (my_method_option).bar = "Some string";
  }
}

Ten en cuenta que, si quieres usar una opción personalizada en un paquete distinto del que se definió, debes anteponer el nombre de la opción al nombre del paquete, como lo harías con los nombres de los tipos. Por ejemplo:

// foo.proto
import "google/protobuf/descriptor.proto";
package foo;
extend google.protobuf.MessageOptions {
  optional string my_option = 51234;
}
// bar.proto
import "foo.proto";
package bar;
message MyMessage {
  option (foo.my_option) = "Hello world!";
}

Por último, dado que las opciones personalizadas son extensiones, se les deben asignar números de campo como cualquier otro campo o extensión. En los ejemplos anteriores, usamos números de campo en el rango 50000-99999. Este rango está reservado para uso interno dentro de organizaciones individuales, por lo que puedes usar números en este rango de forma gratuita para aplicaciones internas. Sin embargo, si quieres usar opciones personalizadas en aplicaciones públicas, es importante que te asegures de que los números de campo sean únicos a nivel global. A fin de obtener números de campo únicos a nivel global, envía una solicitud para agregar una entrada al registro de extensiones global de protobuf. Por lo general, solo se necesita un número de extensión. Puedes declarar varias opciones con un solo número de extensión. Para ello, debes colocarlas en un submensaje:

message FooOptions {
  optional int32 opt1 = 1;
  optional string opt2 = 2;
}

extend google.protobuf.FieldOptions {
  optional FooOptions foo_options = 1234;
}

// usage:
message Bar {
  optional int32 a = 1 [(foo_options).opt1 = 123, (foo_options).opt2 = "baz"];
  // alternative aggregate syntax (uses TextFormat):
  optional int32 b = 2 [(foo_options) = { opt1: 123 opt2: "baz" }];
}

Además, ten en cuenta que cada tipo de opción (a nivel de archivo, a nivel de mensaje, a nivel de campo, etc.) tiene su propio espacio numérico, por lo que, por ejemplo, puedes declarar extensiones de FieldOptions y MessageOptions con el mismo número.

Genera tus clases

Para generar el código Java, Python o C++ que necesitas para trabajar con los tipos de mensajes definidos en un archivo .proto, debes ejecutar el compilador del búfer de protocolo protoc en .proto. Si no instalaste el compilador, descarga el paquete y sigue las instrucciones en el archivo README.

El compilador de protocolo se invoca de la siguiente manera:

protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR path/to/file.proto
  • IMPORT_PATH especifica un directorio en el que se deben buscar archivos .proto cuando se resuelven directivas import. Si se omite, se usa el directorio actual. Para especificar varios directorios de importación, pasa la opción --proto_path varias veces; se buscarán en orden. -I=_IMPORT_PATH_ se puede usar como una forma corta de --proto_path.
  • Puedes proporcionar una o más directivas de salida:

    Para tu comodidad, si el DST_DIR termina en .zip o .jar, el compilador escribirá el resultado en un solo archivo de formato ZIP con el nombre dado. Los resultados de .jar también recibirán un archivo de manifiesto, según lo requiera la especificación Java JAR. Ten en cuenta que, si el archivo de salida ya existe, se reemplazará. El compilador no es lo suficientemente inteligente para agregar archivos a un archivo existente.

  • Debes proporcionar uno o más archivos .proto como entrada. Se pueden especificar varios archivos .proto a la vez. Si bien los archivos tienen un nombre relacionado con el directorio actual, cada uno debe residir en uno de los IMPORT_PATH para que el compilador pueda determinar su nombre canónico.

Ubicación del archivo

Se prefiere no colocar los archivos .proto en el mismo directorio que otras fuentes de lenguaje. Considera crear un subpaquete proto para los archivos .proto, en el paquete raíz de tu proyecto.

La ubicación no debe tener preferencias de idioma.

Cuando trabajas con código Java, es útil colocar los archivos .proto relacionados en el mismo directorio que la fuente de Java. Sin embargo, si algún código que no es de Java usa los mismos protocolos, el prefijo de ruta de acceso ya no tendrá sentido. Por lo tanto, en general, coloca los protocolos en un directorio independiente del lenguaje relacionado, como //myteam/mypackage.

La excepción a esta regla es cuando está claro que los protos se usan solo en un contexto de Java, por ejemplo, para pruebas.

Plataformas compatibles

Para obtener información sobre: