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 idioma (proto3)

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 proto3 del lenguaje del búfer de protocolo: para obtener información sobre la sintaxis proto2, consulta la Guía del lenguaje Proto2.

Esta es una guía de referencia. Para ver un ejemplo paso a paso que usa muchas de las funciones que se describen en este documento, consulta el instructivo del lenguaje que elegiste (actualmente solo proto2, pero pronto habrá más documentación sobre proto3).

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.

syntax = "proto3";

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}
  • La primera línea del archivo especifica que estás usando la sintaxis proto3: si no lo haces, el compilador del búfer de protocolo asumirá que usas proto2. Debe ser la primera línea del archivo que no esté vacía y sin comentarios.
  • 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 de campo 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. Ten en cuenta que los números de campo del 1 al 15 toman un byte para codificar, incluido el número del campo y su tipo (puedes obtener más información sobre este tema en la codificación del 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 del 1 al 15 para los elementos del mensaje más 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 de los búferes de protocolo. El compilador de 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

Los campos de mensaje pueden ser uno de los siguientes:

  • singular: un mensaje con formato correcto puede tener cero o uno de este campo (pero no más de uno). Cuando se usa la sintaxis proto3, esta es la regla de campo predeterminada cuando no se especifican otras reglas de campo para un campo determinado. No puedes determinar si se analizó desde el cable. Se serializará en el cable, a menos que sea el valor predeterminado. Para obtener más información sobre este tema, consulta Presencia de campos.
  • optional: Es igual que singular, excepto que puedes verificar si el valor se estableció de forma explícita. Un campo optional se encuentra en uno de dos estados posibles:
    • el campo esté configurado y contenga un valor que se haya establecido o analizado explícitamente del cable. Se serializará en el cable.
    • si el campo no está configurado y mostrará el valor predeterminado No se serializará en el cable.
  • repeated: Este tipo de campo se puede repetir cero o más veces en un mensaje con formato correcto. Se conservará el orden de los valores repetidos.
  • map: Este es un tipo de campo de clave-valor vinculado. Consulta Maps para obtener más información sobre este tipo de campo.

En proto3, los campos repeated de tipos numéricos escalares usan la codificación packed de forma predeterminada. Puedes obtener más información sobre la codificación packed en Codificación de búfer de protocolo.

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 {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}

message SearchResponse {
 ...
}

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 {
  string query = 1;
  int32 page_number = 2;  // Which page number do we want?
  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";
}

Ten en cuenta que no puedes combinar nombres de campos y números de campos 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 una clase Builder especial para crear instancias de clase de mensaje.
  • Para Kotlin, además del código generado de Java, el compilador genera un archivo .kt para cada tipo de mensaje, que contiene un DSL que se puede usar a fin de simplificar la creación de instancias de mensajes.
  • 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.
  • Para Ruby, el compilador genera un archivo .rb con un módulo de Ruby que contiene tus tipos de mensajes.
  • Para Objective-C, el compilador genera un archivo pbobjc.h y pbobjc.m desde cada .proto, con una clase para cada tipo de mensaje descrito en tu archivo.
  • Para C#, el compilador genera un archivo .cs a partir de cada .proto, con una clase para cada tipo de mensaje descrito en tu archivo.
  • Para Dart, el compilador genera un archivo .pb.dart con una clase para cada tipo de mensaje en tu archivo.

Si quieres obtener más información sobre cómo usar las API para cada idioma, sigue el instructivo del idioma que elegiste (disponible próximamente para las versiones proto3). Para obtener aún más detalles de la API, consulta la referencia de la API relevante (las versiones proto3 también estarán disponibles próximamente).

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/Kotlin[1] Tipo de Python[3] Tipo de Go Tipo de Ruby Tipo de C# Tipo de PHP Tipo de Dart
double double double float float64 Número de punto flotante double float double
float float float float float32 Número de punto flotante float float double
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 Fixnum o Bignum (según sea necesario) int integer int
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[4] int64 Bignum long entero/string[6] Int64
uint32 Usa codificación de longitud variable. uint32 número entero[2] int/long[4] uint32 Fixnum o Bignum (según sea necesario) uint integer int
uint64 Usa codificación de longitud variable. uint64 largo[2] int/long[4] uint64 Bignum Ulong entero/string[6] Int64
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 Fixnum o Bignum (según sea necesario) int integer int
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[4] int64 Bignum long entero/string[6] Int64
Fijo32 Siempre cuatro bytes. Es más eficiente que uint32 si los valores suelen ser mayores que 228. uint32 número entero[2] int/long[4] uint32 Fixnum o Bignum (según sea necesario) uint integer int
fijo64 Siempre ocho bytes. Es más eficiente que uint64 si los valores suelen ser mayores que 256. uint64 largo[2] int/long[4] uint64 Bignum Ulong entero/string[6] Int64
fijo32 Siempre cuatro bytes. int32 int int int32 Fixnum o Bignum (según sea necesario) int integer int
fijas64 Siempre ocho bytes. int64 long int/long[4] int64 Bignum long entero/string[6] Int64
bool bool boolean bool bool TrueClass/FalseClass bool boolean bool
string Una string siempre debe contener codificación UTF-8 o texto ASCII de 7 bits, y no puede tener más de 232. string String str/unicode[5] string String (UTF‐8) string string String
bytes Puede contener cualquier secuencia de bytes arbitraria, de no más de 232. string ByteString str (Python 2)
bytes (Python 3)
[]byte Cadena (ASCII-8BIT) ByteString string Lista

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] Kotlin utiliza los tipos correspondientes de Java, incluso para tipos sin firma, a fin de garantizar la compatibilidad en bases de código mixtas de Java/Kotlin.

[2] 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.

[3] 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.

[4] Los números enteros de 32 bits o 64 bits siempre están representados 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].

[5] Las strings de Python se representan como unicode en la decodificación, pero pueden ser str si se proporciona una string ASCII (esto está sujeto a cambios).

[6] El número entero se usa en máquinas de 64 bits y la string se usa en máquinas de 32 bits.

Valores predeterminados

Cuando se analiza un mensaje, si el mensaje codificado no contiene un elemento singular en particular, el campo correspondiente en el objeto analizado se establece en el valor predeterminado para ese campo. Estos valores predeterminados son específicos del tipo:

  • Para las strings, el valor predeterminado es una string vacía.
  • Para los bytes, el valor predeterminado es la cantidad de bytes vacíos.
  • Para booleanos, el valor predeterminado es falso.
  • Para los tipos numéricos, el valor predeterminado es cero.
  • Para las enumeraciones, el valor predeterminado es el primer valor de enumeración definido, que debe ser 0.
  • Para los campos de mensaje, el campo no está configurado. Su valor exacto depende del lenguaje. Consulta la guía de código generado para obtener más detalles.

El valor predeterminado para los campos repetidos está vacío (por lo general, una lista vacía en el idioma adecuado).

Ten en cuenta que para los campos de mensajes escalares, una vez que se analiza un mensaje, no hay forma de saber si un campo se estableció de forma explícita con el valor predeterminado (por ejemplo, si se configuró un booleano en false) o si no se estableció ninguno: debes tener esto en cuenta cuando definas tus tipos de mensajes. Por ejemplo, no tengas un valor booleano que active algunos comportamientos cuando se establece en false si no quieres que ese comportamiento también ocurra de forma predeterminada. También ten en cuenta que si un campo de mensaje escalar está configurado de forma predeterminada, el valor no se serializará en el cable.

Consulta la guía de código generado del lenguaje que elegiste para obtener más detalles sobre cómo funcionan los valores predeterminados en el código generado.

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 sencilla si agregas un enum a tu definición de mensajes con una constante para cada valor posible.

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 {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
  Corpus corpus = 4;
}

Como puedes ver, la primera constante de la enumeración Corpus se asigna a cero: cada definición de enumeración debe contener una constante que se asigne a cero como su primer elemento. Esto se debe a los siguientes motivos:

  • Debe haber un valor de cero, por lo que podemos usar 0 como valor predeterminado numérico.
  • El valor cero debe ser el primer elemento, para que sea compatible con la semántica de proto2, en la que el primer valor de enumeración siempre es el predeterminado.

Puedes definir alias si asignas el mismo valor a diferentes constantes de enumeración. Para ello, debes configurar la opción allow_alias en true; de lo contrario, el compilador del 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, como en el ejemplo anterior, o fuera de ella. Estos enum se pueden volver a usar en cualquier definición de mensaje 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 un .proto que usa un enum, el código generado tendrá un enum correspondiente para Java, Kotlin o C++, o una clase EnumDescriptor especial para Python que se usa a fin de crear un conjunto de constantes simbólicas con valores de números enteros en la clase generada en el entorno de ejecución.

Durante la deserialización, los valores de enumeración no reconocidos se conservarán en el mensaje, aunque la forma en que esto se representa cuando se deserializa el mensaje depende del lenguaje. En los lenguajes que admiten tipos de enumeración abierta con valores fuera del rango de símbolos especificados, como C++ y Go, el valor de enumeración desconocido simplemente se almacena como su representación de número entero subyacente. En lenguajes con tipos de enumeración cerrada, como Java, se usa un caso en la enumeración para representar un valor no reconocido y se puede acceder al número entero subyacente con descriptores de acceso especiales. En cualquier caso, si el mensaje está serializado, el valor no reconocido se serializará con el mensaje.

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 results = 1;
}

message Result {
  string url = 1;
  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 proto2

Es posible importar tipos de mensajes proto2 y usarlos en tus mensajes proto3, y viceversa. Sin embargo, las enumeraciones proto2 no se pueden usar directamente en la sintaxis proto3 (está bien si un mensaje proto2 importado las usa).

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 {
    string url = 1;
    string title = 2;
    repeated string snippets = 3;
  }
  repeated Result results = 1;
}

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

message SomeOtherMessage {
  SearchResponse.Result result = 1;
}

Puedes anidar los mensajes tan profundamente como quieras:

message Outer {                  // Level 0
  message MiddleAA {  // Level 1
    message Inner {   // Level 2
      int64 ival = 1;
      bool  booly = 2;
    }
  }
  message MiddleBB {  // Level 1
    message Inner {   // Level 2
      int32 ival = 1;
      bool  booly = 2;
    }
  }
}

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. Solo recuerda las siguientes reglas:

  • No cambies los números de campo de los campos existentes.
  • Si agregas campos nuevos, cualquier mensaje serializado por código con tu formato de mensaje "anterior" aún se puede analizar con tu nuevo código generado. Debes tener en cuenta los valores predeterminados para estos elementos a fin de que el código nuevo pueda interactuar de forma correcta 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 anterior: los objetos binarios anteriores simplemente ignoran el campo nuevo cuando se realiza un análisis. Consulta la sección Campos desconocidos para obtener más detalles.
  • Los campos se pueden quitar, siempre y cuando no se vuelva a usar el número de campo en tu 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.
  • 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 los campos string, bytes y de mensaje, los campos singulares son compatibles con los campos repeated. Dados los datos serializados de un campo repetido como entrada, los clientes que esperan que este campo sea singular 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 bool y las enumeraciones. Los campos repetidos de tipos numéricos se pueden serializar en el formato empaquetado, que no se analizará correctamente cuando se espere un campo singular.
  • enum es compatible con int32, uint32, int64 y uint64 en términos de formato de cable (ten en cuenta que los valores se truncarán si no son adecuados). Sin embargo, ten en cuenta que el código del cliente puede tratarlos de manera diferente cuando se deserializa el mensaje: por ejemplo, los tipos de proto3 enum no reconocidos se conservarán en el mensaje, pero la forma en que esto se representa cuando el mensaje se deserializa depende del lenguaje. Los campos Int solo conservan su valor.
  • 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.

Campos desconocidos

Los campos desconocidos son datos serializados en búfer de protocolo con el formato correcto que representan campos que el analizador no reconoce. Por ejemplo, cuando un objeto binario anterior analiza los datos que envía un objeto binario nuevo con campos nuevos, esos campos nuevos se convierten en campos desconocidos en el objeto binario anterior.

Originalmente, los mensajes proto3 siempre descartaban campos desconocidos durante el análisis, pero en la versión 3.5 volvimos a introducir la conservación de los campos desconocidos para que coincidan con el comportamiento de proto2. En las versiones 3.5 y posteriores, los campos desconocidos se retienen durante el análisis y se incluyen en el resultado serializado.

Cualquiera

El tipo de mensaje Any te permite usar mensajes como tipos incorporados sin tener su definición .proto. Una Any contiene un mensaje serializado arbitrario como bytes, junto con una URL que actúa como identificador único global y se resuelve para ese tipo de mensaje. Para usar el tipo Any, debes importar google/protobuf/any.proto.

import "google/protobuf/any.proto";

message ErrorStatus {
  string message = 1;
  repeated google.protobuf.Any details = 2;
}

El tipo de URL predeterminado para un tipo de mensaje determinado es type.googleapis.com/_packagename_._messagename_.

Las diferentes implementaciones de lenguaje admitirán asistentes de bibliotecas en tiempo de ejecución para empaquetar y descomprimir valores Any de manera segura; por ejemplo, en Java, el tipo Any tendrá descriptores de acceso especiales pack() y unpack(), mientras que en C++ habrá métodos PackFrom() y UnpackTo():

// Storing an arbitrary message type in Any.
NetworkErrorDetails details = ...;
ErrorStatus status;
status.add_details()->PackFrom(details);

// Reading an arbitrary message from Any.
ErrorStatus status = ...;
for (const google::protobuf::Any& detail : status.details()) {
  if (detail.Is<NetworkErrorDetails>()) {
    NetworkErrorDetails network_error;
    detail.UnpackTo(&network_error);
    ... processing network_error ...
  }
}

Actualmente, las bibliotecas del entorno de ejecución para trabajar con tipos Any están en desarrollo.

Si ya estás familiarizado con la sintaxis proto2, Any puede contener mensajes proto3 arbitrarios, similar a los mensajes proto2 que pueden permitir las extensiones.

Uno de

Si tienes un mensaje con muchos campos y en los que se configurará al menos un campo al mismo tiempo, puedes aplicar este comportamiento y ahorrar memoria con una de las funciones.

Uno de los campos es como los campos normales, excepto todos los campos en 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.

Ten en cuenta que, si se establecen varios valores, el último valor establecido según el orden en el proto reemplazará todos los anteriores.

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, excepto los campos map y repeated.

En el código generado, uno de los campos tiene los mismos métodos get y set que los campos normales. También obtienes un método especial para verificar qué valor está configurado en el que se configuró. Puedes obtener más información sobre la API de One del idioma que elegiste 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_EQ(message.name(), "");
    // Calling mutable_sub_message() will clear the name field and will set
    // sub_message to a new instance of SubMessage with none of its fields set
    message.mutable_sub_message();
    CHECK(message.name().empty());
    
  • Si el analizador encuentra varios miembros del mismo cable en el cable, solo se usa el último que se vio en el mensaje analizado.

  • 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_EQ(msg2.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 a uno de uno o quitarlos: 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 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;

  • Los campos de mapa no pueden ser repeated.
  • 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 del mapa se encuentren 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.
  • Si proporcionas una clave, pero no un valor para un campo de mapa, el comportamiento cuando el campo se serializa depende del idioma. En C++, Java, Kotlin y Python, el valor predeterminado para el tipo es serializado, mientras que en otros lenguajes no se serializa nada.

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

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 {
  key_type key = 1;
  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 {
  ...
  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 y Kotlin, 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 del paquete, ya que los módulos de Python se organizan según su ubicación en el sistema de archivos.
  • En Go, el paquete se usa como el nombre del paquete de Go, a menos que proporciones un option go_package de forma explícita en el archivo .proto.
  • En Ruby, las clases generadas se unen dentro de los espacios de nombres anidados de Ruby, se convierten al estilo de mayúsculas de Ruby requerido (primera letra en mayúscula). Si el primer carácter no es una letra, PB_ está antepuesto. Por ejemplo, Open estaría en el espacio de nombres Foo::Bar.
  • En C#, el paquete se usa como el espacio de nombres después de la conversión a PascalCase, a menos que proporciones un option csharp_namespace en tu archivo .proto de forma explícita. Por ejemplo, Open estaría en el espacio de nombres Foo.Bar.

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);
}

El sistema de RPC más sencillo de usar con búferes de protocolo es 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 mediante un complemento de compilador de búfer de protocolo especial.

Si no quieres usar gRPC, también es posible usar búferes de protocolo con tu propia implementación de RPC. Puedes obtener más información sobre esto en la Guía de idiomas de Proto2.

También hay varios proyectos de terceros en curso que desarrollan 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.

Asignación JSON

Proto3 admite una codificación canónica en JSON, lo que facilita el uso compartido de datos entre sistemas. La codificación se describe según el tipo en la tabla a continuación.

Cuando se analizan datos codificados en JSON en un búfer de protocolo, si falta un valor o si su valor es null, se interpretará como el valor predeterminado correspondiente.

Cuando se genera un resultado con codificación JSON a partir de un búfer de protocolo, si un campo protobuf tiene el valor predeterminado y, si no admite la presencia de este, se omitirá de la configuración predeterminada. Una implementación puede proporcionar opciones para incluir campos con valores predeterminados en el resultado.

Un campo proto3 que se define con la palabra clave optional admite la presencia de campo. Los campos que tienen un valor establecido y que admiten la presencia de campos siempre incluyen el valor del campo en la salida codificada en JSON, incluso si es el valor predeterminado.

proto3 JSON Ejemplo de JSON Notas
mensaje objeto {"fooBar": v, "g": null, …} Genera objetos JSON. Los nombres de los campos de mensaje se asignan a lowCamelCase y se convierten en claves de objeto JSON. Si se especifica la opción del campo json_name, se usará el valor especificado como clave. Los analizadores aceptan el nombre lowCamelCase (o el que especifica la opción json_name) y el nombre del campo proto original. null es un valor aceptado para todos los tipos de campo y se trata como el valor predeterminado del tipo de campo correspondiente.
Enum string "FOO_BAR" Se usa el nombre del valor enum especificado en proto. Los analizadores aceptan nombres de enumeración y valores de número entero.
map<K,V> objeto {"k": v, …} Todas las claves se convierten en strings.
V repetida array [v, …] null se acepta como la lista vacía [].
bool true, false true, false
string string "Hello World!"
bytes string base64 "YWJjMTIzIT8kKiYoKSctPUB+" El valor JSON será el dato codificado como una string que usa codificación Base64 estándar con rellenos. Se acepta la codificación en base64 estándar o segura para URL con/sin paddings.
int32, corre32, uint32 número 1, -10, 0 El valor JSON será un número decimal. Se aceptan números o strings.
int64, fixed64, uint64 string "1", "-10" El valor JSON será una string decimal. Se aceptan números o strings.
doble, flotante número 1.1, -10.0, 0, "NaN", "Infinity" El valor JSON será un número o uno de los valores de string especiales “NaN”, “Infinity” y “-Infinity”. Se aceptan números o strings. También se acepta la notación de exponentes. -0 se considera equivalente a 0.
Cualquiera object {"@type": "url", "f": v, … } Si el Any contiene un valor que tiene una asignación JSON especial, se convertirá de la siguiente manera: {"@type": xxx, "value": yyy}. De lo contrario, el valor se convertirá en un objeto JSON y el campo "@type" se insertará para indicar el tipo de datos real.
Marca de tiempo string "1972-01-01T10:00:20.021Z" Usa RFC 3339, en el que la salida generada siempre se normalizará en Z y usa 0, 3, 6 o 9 dígitos fraccionarios. También se admiten desplazamientos distintos de "Z".
Duración string "1.000340012s", "1s" El resultado generado siempre contiene 0, 3, 6 o 9 dígitos fraccionarios, según la precisión requerida, seguido del sufijo "s". Se aceptan dígitos fraccionarios (y ninguno) si se ajustan a la precisión en nanosegundos y se requiere el sufijo “s”.
Estructural object { … } Cualquier objeto JSON. Consulta los struct.proto.
Tipos de wrapper varios tipos 2, "2", "foo", true, "true", null, 0, … Los wrappers usan la misma representación en JSON que el tipo primitivo unido, excepto que se permite y se conserva null durante la conversión y transferencia de datos.
FieldMask string "f.fooBar,h" Consulta los field_mask.proto.
ListValue array [foo, bar, …]
Valor valor Cualquier valor JSON. Consulta google.protobuf.Value para obtener más información.
NullValue null JSON nulo
Vacío objeto {} Un objeto JSON vacío

Opciones JSON

Una implementación JSON de proto3 puede proporcionar las siguientes opciones:

  • Emite campos con valores predeterminados: Los campos con valores predeterminados se omiten de forma predeterminada en los resultados de JSON proto3. Una implementación puede proporcionar una opción para anular este comportamiento y los campos de salida con sus valores predeterminados.
  • Ignorar campos desconocidos: El analizador JSON Proto3 debe rechazar los campos desconocidos de forma predeterminada, pero puede proporcionar una opción para ignorar los campos desconocidos en el análisis.
  • Usar el nombre de campo de proto en lugar de nombre de lowCamelCase: De forma predeterminada, la impresora JSON proto3 debe convertir el nombre de campo en lowCamelCase y usarlo como el nombre JSON. Una implementación puede proporcionar una opción para usar el nombre del campo de proto como el nombre JSON en su lugar. Los analizadores JSON de Proto3 deben aceptar el nombre convertido de lowCamelCase y el nombre del campo de proto.
  • Emite valores de enumeración como números enteros en lugar de strings: El nombre de un valor de enumeración se usa de forma predeterminada en el resultado de JSON. En su lugar, se puede proporcionar una opción para usar el valor numérico del valor enum.

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 generadas de Java o Kotlin. 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, los paquetes de .proto generalmente no son buenos paquetes de Java, ya que no se espera que los paquetes de proto comiencen con nombres de dominio inverso. Si no se genera código Java o Kotlin, 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_enable_arenas (opción de archivo): Habilita la asignación de Arena para el código generado de C++.

  • objc_class_prefix (opción de archivo): Establece el prefijo de clase de Objective-C que se antepone a todas las clases y enumeraciones generadas por Objective-C de este .proto. No hay ningún valor predeterminado. Debes usar prefijos que tengan entre 3 y 5 caracteres en mayúscula, según se recomienda en Apple. Ten en cuenta que Apple reserva los prefijos de 2 letras.

  • 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.

    int32 old_field = 6 [deprecated = true];
    

Opciones personalizadas

Los búferes de protocolo también te permiten definir y usar tus propias opciones. Esta es una función avanzada que la mayoría de las personas no necesitan. Si crees que necesitas crear tus propias opciones, consulta la Guía de lenguaje Proto2 para obtener más detalles. Ten en cuenta que la creación de opciones personalizadas utiliza extensiones, que solo se permiten para opciones personalizadas en proto3.

Genera tus clases

Para generar el código de Java, Kotlin, Python, C++, Go, Ruby, Objective-C o C# que necesitas para trabajar con los tipos de mensajes definidos en un archivo .proto, debes ejecutar el compilador de búferes de protocolo protoc en .proto. Si no instalaste el compilador, descarga el paquete y sigue las instrucciones en el archivo README. Para Go, también debes instalar un complemento generador de código especial para el compilador. Puedes encontrar esta información y las instrucciones de instalación en el repositorio golang/protobuf en GitHub.

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 --go_out=DST_DIR --ruby_out=DST_DIR --objc_out=DST_DIR --csharp_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: