Cách xác định một trường tùy chọn trong protobuf 3


106

Tôi cần chỉ định một tin nhắn có trường tùy chọn trong protobuf (cú pháp proto3). Về cú pháp proto 2, thông điệp tôi muốn diễn đạt là:

message Foo {
    required int32 bar = 1;
    optional int32 baz = 2;
}

Theo hiểu biết của tôi, khái niệm "tùy chọn" đã bị loại bỏ khỏi cú pháp proto 3 (cùng với khái niệm bắt buộc). Mặc dù không rõ ràng về lựa chọn thay thế - sử dụng giá trị mặc định để nói rằng một trường chưa được chỉ định từ người gửi, để lại sự không rõ ràng nếu giá trị mặc định thuộc về miền giá trị hợp lệ (ví dụ như kiểu boolean).

Vì vậy, tôi phải mã hóa thông báo trên như thế nào? Cảm ơn bạn.


Cách tiếp cận dưới đây có phải là một giải pháp hợp lý? message NoBaz {} message Foo {int32 bar = 1; oneof baz {NoBaz undefined = 2; int32 được xác định = 3; }; }
MaxP

2
một phiên bản Proto 2 của câu hỏi này , nếu những người khác tìm thấy điều này nhưng đang sử dụng Proto 2.
chwarr

1
proto3 về cơ bản làm cho tất cả các trường là tùy chọn. Tuy nhiên, đối với các đại lượng vô hướng, họ không thể phân biệt giữa "trường không được đặt" và "trường được đặt nhưng ở giá trị mặc định." Nếu bạn bọc vô hướng của mình trong một đơn vị, ví dụ - message blah {oneof v1 {int32 foo = 1; }}, sau đó bạn có thể kiểm tra lại xem foo đã thực sự được đặt hay chưa. Đối với Python ít nhất, bạn có thể thao tác trực tiếp trên foo như thể nó không nằm trong oneof và bạn có thể hỏi HasField ("foo").
jschultz410

1
@MaxP Có thể bạn có thể thay đổi câu trả lời được chấp nhận thành stackoverflow.com/a/62566052/66465 vì phiên bản protobuf 3 mới hơn hiện đã cóoptional
SebastianK

Câu trả lời:


40

Kể từ bản phát hành protobuf 3.12 , proto3 đã hỗ trợ sử dụng optionaltừ khóa (giống như trong proto2) để cung cấp thông tin hiện diện trường vô hướng.

syntax = "proto3";

message Foo {
    int32 bar = 1;
    optional int32 baz = 2;
}

Phương thức A has_baz()/ hasBaz()được tạo cho optionaltrường trên, giống như trong proto2.

Dưới mui xe, protoc xử lý hiệu quả một optionaltrường như thể nó được khai báo bằng cách sử dụng oneoftrình bao bọc, như câu trả lời của CyberSnoopy gợi ý:

message Foo {
    int32 bar = 1;
    oneof optional_baz {
        int32 baz = 2;
    }
}

Nếu bạn đã sử dụng phương pháp đó, bạn có thể xóa phần khai báo thư của mình (chuyển từ oneofsang optional), vì định dạng dây là giống nhau.

Bạn có thể tìm thấy thông tin chi tiết về sự hiện diện của trường và optionaltrong proto3 trong Ghi chú ứng dụng: Tài liệu về sự hiện diện của trường .

Trong bản phát hành 3.12, chức năng này yêu cầu chuyển --experimental_allow_proto3_optionalcờ tới protoc. Các thông báo tính năng cho biết họ sẽ “thường có sẵn hy vọng trong 3.13”.

Cập nhật tháng 10 năm 2020: Tính năng này vẫn được coi là thử nghiệm (bắt buộc phải gắn cờ) trong bản phát hành 3.13 .


1
Bạn có tình cờ biết cách vượt qua cờ trong C # không?
James Hancock

Đây là câu trả lời tốt nhất bây giờ mà proto3 đã thêm cú pháp tốt hơn. Chú thích tuyệt vời Jarad!
Evan Moran

Chỉ cần thêm cho optional int xyz: 1) has_xyzphát hiện nếu giá trị tùy chọn đã được đặt 2) clear_xyzsẽ bỏ đặt giá trị. Thông tin thêm tại đây: github.com/protocolbuffers/protobuf/blob/master/docs/…
Evan Moran

@JamesHancock hay Java?
Tobi Akinyemi

@TobiAkinyemi ??
James Hancock

123

Trong proto3, tất cả các trường là "tùy chọn" (trong đó nó không phải là lỗi nếu người gửi không đặt chúng). Tuy nhiên, các trường không còn là "nullable" nữa, do đó không có cách nào để phân biệt sự khác biệt giữa trường được đặt thành giá trị mặc định rõ ràng so với trường chưa được đặt.

Nếu bạn cần trạng thái "null" (và không có giá trị nằm ngoài phạm vi nào mà bạn có thể sử dụng cho việc này) thì thay vào đó, bạn sẽ cần mã hóa trường này thành một trường riêng biệt. Ví dụ, bạn có thể làm:

message Foo {
  bool has_baz = 1;  // always set this to "true" when using baz
  int32 baz = 2;
}

Ngoài ra, bạn có thể sử dụng oneof:

message Foo {
  oneof baz {
    bool baz_null = 1;  // always set this to "true" when null
    int32 baz_value = 2;
  }
}

Các oneofphiên bản là rõ ràng hơn và hiệu quả hơn trên dây nhưng đòi hỏi sự hiểu biết như thế nào oneofgiá trị làm việc.

Cuối cùng, một lựa chọn hoàn toàn hợp lý khác là gắn bó với proto2. Proto2 không bị phản đối và trên thực tế, nhiều dự án (bao gồm cả bên trong Google) phụ thuộc rất nhiều vào các tính năng của proto2 đã bị loại bỏ trong proto3, do đó chúng có thể sẽ không bao giờ chuyển đổi. Vì vậy, thật an toàn để tiếp tục sử dụng nó trong tương lai gần.


Tương tự như giải pháp của bạn, trong nhận xét của tôi, tôi đã đề xuất sử dụng oneof với giá trị thực và kiểu null (thông báo trống). Bằng cách này, bạn không cần bận tâm đến giá trị boolean (giá trị này không liên quan, vì nếu có boolean, thì không có baz_value) Đúng không?
MaxP

2
@MaxP Giải pháp của bạn hoạt động nhưng tôi muốn giới thiệu một boolean trên một thông báo trống. Một trong hai sẽ mất hai byte trên dây nhưng thông báo trống sẽ mất nhiều CPU, RAM và khối mã được tạo hơn để xử lý.
Kenton Varda

13
Tôi tìm thấy thông báo Foo {oneof baz {int32 baz_value = 1; }} hoạt động khá tốt.
CyberSnoopy

@CyberSnoopy Bạn có thể đăng nó như một câu trả lời không? Giải pháp của bạn hoạt động hoàn hảo và thanh lịch.
Cheng Chen

@CyberSnoopy Bạn có tình cờ gặp bất kỳ sự cố nào khi gửi tin nhắn phản hồi có cấu trúc như: message FooList {lặp lại Foo foos = 1; }? Giải pháp của bạn thật tuyệt nhưng tôi đang gặp sự cố khi gửi FooList dưới dạng phản hồi của máy chủ.
CaffeinateO thường

95

Một cách là sử dụng oneofnhư gợi ý trong câu trả lời được chấp nhận.

Một cách khác là sử dụng các đối tượng trình bao bọc. Bạn không cần phải tự viết chúng vì google đã cung cấp cho chúng:

Ở đầu tệp .proto của bạn, hãy thêm nhập này:

import "google/protobuf/wrappers.proto";

Giờ đây, bạn có thể sử dụng các trình bao bọc đặc biệt cho mọi kiểu đơn giản:

DoubleValue
FloatValue
Int64Value
UInt64Value
Int32Value
UInt32Value
BoolValue
StringValue
BytesValue

Vì vậy, để trả lời câu hỏi ban đầu, cách sử dụng một trình bao bọc như vậy có thể như sau:

message Foo {
    int32 bar = 1;
    google.protobuf.Int32Value baz = 2;
}

Bây giờ, ví dụ trong Java, tôi có thể làm những thứ như:

if(foo.hasBaz()) { ... }


3
Cái này hoạt động ra sao? Khi nào baz=nullvà khi nào bazkhông được thông qua, cả hai trường hợp đều hasBaz()nói false!
mayankcpdixit

Ý tưởng rất đơn giản: bạn sử dụng các đối tượng wrapper hay nói cách khác là các kiểu do người dùng xác định. Các đối tượng trình bao bọc này được phép thiếu. Ví dụ Java mà tôi cung cấp đã hoạt động tốt cho tôi khi làm việc với gRPC.
VM4

Vâng! Tôi hiểu ý tưởng chung, nhưng tôi muốn thấy nó hoạt động. Những gì tôi không hiểu là: (ngay cả trong đối tượng wrapper) " Làm thế nào để xác định mất tích và các giá trị null wrapper? "
mayankcpdixit

2
Đây là con đường để đi. Với C #, mã được tạo sẽ tạo ra các thuộc tính Nullable <T>.
Aaron Hudon

5
Tốt hơn so với awsner ban đầu!
Dev Aggarwal

32

Dựa trên câu trả lời của Kenton, một giải pháp đơn giản nhưng hiệu quả trông giống như sau:

message Foo {
    oneof optional_baz { // "optional_" prefix here just serves as an indicator, not keyword in proto2
        int32 baz = 1;
    }
}

làm thế nào để điều này thể hiện ký tự tùy chọn?
JFFIGK

19
Về cơ bản, oneof được đặt tên kém. Nó có nghĩa là "nhiều nhất một trong số". Luôn luôn có một giá trị rỗng có thể có.
ecl3ctic

Nếu không được đặt, trường hợp giá trị sẽ là None(trong C #) - hãy xem kiểu enum cho ngôn ngữ bạn chọn.
nitzel

Vâng, đây có lẽ là cách tốt nhất để lột da con mèo này trong proto3 - ngay cả khi nó làm cho .proto xấu đi một chút.
jschultz410

Tuy nhiên, nó phần nào ngụ ý rằng bạn có thể giải thích sự vắng mặt của một trường là đặt nó thành giá trị null một cách rõ ràng. Nói cách khác, có một số sự không rõ ràng giữa 'trường tùy chọn không được chỉ định' và 'trường không được chỉ định một cách cố ý có nghĩa là nó rỗng'. Nếu bạn quan tâm đến mức độ chính xác đó, thì bạn có thể thêm một trường google.protobuf.NullValue bổ sung vào trường cho phép bạn phân biệt giữa 'trường không được chỉ định', 'trường được chỉ định là giá trị X' và 'trường được chỉ định là null' . Đó là điều khá khó hiểu, nhưng đó là vì proto3 không hỗ trợ trực tiếp null như JSON.
jschultz410

7

Để mở rộng đề xuất của @cybersnoopy tại đây

nếu bạn có tệp .proto với thông báo như vậy:

message Request {
    oneof option {
        int64 option_value = 1;
    }
}

Bạn có thể sử dụng các tùy chọn trường hợp được cung cấp (mã do java tạo) :

Vì vậy, bây giờ chúng ta có thể viết một số mã như sau:

Request.OptionCase optionCase = request.getOptionCase();
OptionCase optionNotSet = OPTION_NOT_SET;

if (optionNotSet.equals(optionCase)){
    // value not set
} else {
    // value set
}

Trong Python, nó thậm chí còn đơn giản hơn. Bạn chỉ có thể thực hiện request.HasField ("option_value"). Ngoài ra, nếu bạn có một loạt các singleton giống như thế này bên trong thư của mình thì bạn có thể truy cập trực tiếp vào các đại lượng vô hướng chứa chúng giống như một đại lượng vô hướng bình thường.
jschultz410


-1

Một cách khác là bạn có thể sử dụng mặt nạ bit cho mỗi trường tùy chọn. và đặt các bit đó nếu các giá trị được đặt và đặt lại các bit có giá trị không được đặt

enum bitsV {
    baz_present = 1; // 0x01
    baz1_present = 2; // 0x02

}
message Foo {
    uint32 bitMask;
    required int32 bar = 1;
    optional int32 baz = 2;
    optional int32 baz1 = 3;
}

Khi phân tích cú pháp, hãy kiểm tra giá trị của bitMask.

if (bitMask & baz_present)
    baz is present

if (bitMask & baz1_present)
    baz1 is present

-2

bạn có thể tìm nếu một cái đã được khởi tạo bằng cách so sánh các tham chiếu với phiên bản mặc định:

GRPCContainer container = myGrpcResponseBean.getContainer();
if (container.getDefaultInstanceForType() != container) {
...
}

1
Đây không phải là một cách tiếp cận chung tốt vì rất thường giá trị mặc định là giá trị hoàn toàn có thể chấp nhận được đối với trường và trong trường hợp đó, bạn không thể phân biệt giữa "trường vắng mặt" và "trường có nhưng được đặt thành mặc định."
jschultz410
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.