प्रोटोबुफ़ 3 में एक वैकल्पिक क्षेत्र को कैसे परिभाषित करें


111

मुझे प्रोटोबुफ़ (प्रोटो 3 सिंटैक्स) में एक वैकल्पिक फ़ील्ड के साथ एक संदेश निर्दिष्ट करने की आवश्यकता है। प्रोटो 2 सिंटैक्स के संदर्भ में, मैं जो संदेश व्यक्त करना चाहता हूं वह कुछ इस तरह है:

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

मेरी समझ से "वैकल्पिक" अवधारणा को सिंटैक्स प्रोटो 3 (आवश्यक अवधारणा के साथ) से हटा दिया गया है। हालांकि यह विकल्प स्पष्ट नहीं है - डिफ़ॉल्ट मान का उपयोग करके यह बताना कि किसी फ़ील्ड को प्रेषक से निर्दिष्ट नहीं किया गया है, एक अस्पष्टता छोड़ देता है यदि डिफ़ॉल्ट मान मान्य मान डोमेन से संबंधित है (उदाहरण के लिए बूलियन प्रकार पर विचार करें)।

तो, मुझे ऊपर दिए गए संदेश को कैसे एनकोड करना चाहिए? धन्यवाद।


एक ध्वनि समाधान के नीचे दृष्टिकोण है? संदेश NoBaz {} संदेश फू {int32 बार = 1; aof baz {NoBaz अपरिभाषित = 2; int32 परिभाषित = 3; }; }
मैक्सपी


1
प्रोटो 3 मूल रूप से सभी क्षेत्रों को वैकल्पिक बनाता है। हालांकि, स्केलर के लिए, उन्होंने "फ़ील्ड सेट नहीं" और "फ़ील्ड सेट लेकिन डिफ़ॉल्ट मान के बीच अंतर करना असंभव बना दिया।" यदि आप अपने स्केलर को एक सिंगलटन में लपेटते हैं जैसे - संदेश ब्लाह {वनऑफ v1 {इंट 32 फू = 1; }}, तो आप फिर से जांच कर सकते हैं कि क्या वास्तव में फू सेट किया गया था या नहीं। कम से कम पायथन के लिए, आप फू पर सीधे काम कर सकते हैं जैसे कि यह एक ओफ के अंदर नहीं था और आप हैसफील्ड ("फू") पूछ सकते हैं।
jschultz410

1
@MaxP हो सकता है कि आप स्वीकार किए गए उत्तर को stackoverflow.com/a/62566052/66465 पर बदल सकते हैं क्योंकि प्रोटोबुफ़ 3 के नए संस्करण में अब हैoptional
सेबस्टियनके

जवाबों:


54

प्रोटोबॉफ़ रिलीज़ 3.12 के बाद से , प्रोटो 3 को optionalस्केलर फ़ील्ड की उपस्थिति की जानकारी देने के लिए कीवर्ड (केवल प्रोटो 2 के रूप में) का उपयोग करने के लिए प्रायोगिक समर्थन है ।

syntax = "proto3";

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

has_baz()/ hasBaz()विधि optionalऊपर क्षेत्र के लिए उत्पन्न होता है, जैसे कि यह प्रोटो 2 में था।

हुड के तहत, प्रोटो प्रभावी ढंग से एक optionalक्षेत्र का इलाज करता है जैसे कि इसे एक oneofरैपर का उपयोग करके घोषित किया गया था , जैसा कि साइबरसैनोपी का उत्तर बताता है:

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

आप पहले से ही है कि दृष्टिकोण का उपयोग किया है, तो आप अपने संदेश घोषणाओं (से स्विच को साफ करने में सक्षम हो जाएगा oneofकरने के लिए optional) एक बार proto3 optionalसमर्थन, प्रयोगात्मक स्थिति से स्नातक के बाद से तार प्रारूप में ही है।

आप क्षेत्र की उपस्थिति के बारे optionalमें और आवेदन पत्र में प्रोटो 3 में नॉटी-ग्रिट्टी विवरण पा सकते हैं : फील्ड उपस्थिति डॉक।

--experimental_allow_proto3_optionalरिलीज 3.12 में इस कार्यक्षमता का उपयोग करने के लिए प्रोटोकोल को ध्वज पारित करें । सुविधा घोषणा कहना है कि वह "उम्मीद है कि आम तौर पर उपलब्ध 3.13 में" हो जाएगा।

नवम्बर 2020 अद्यतन: इस सुविधा को अभी भी 3.14 रिलीज़ में प्रायोगिक (ध्वज की आवश्यकता) माना जाता है । उन्नति होने के संकेत हैं ।


3
क्या आपको पता है कि C # में झंडे को कैसे पारित किया जाए?
जेम्स हैनकॉक

यह अब सबसे अच्छा जवाब है कि प्रोटो 3 ने बेहतर सिंटैक्स जोड़ा है। महान कॉलआउट जराड!
इवान मोरन

बस के लिए जोड़ने के लिए optional int xyz: 1) का has_xyzपता लगाता है कि अगर वैकल्पिक मूल्य 2 सेट किया गया था) clear_xyzमूल्य को परेशान करेगा। यहाँ अधिक जानकारी: github.com/protocolbuffers/protobuf/blob/master/docs/…
Evan Moran

@JamesHancock या जावा?
तोबी अकिनेमि

1
@ JONásBalázs - 3.perperimental_allow_proto3_optional ध्वज को प्रोटोक 3. रिलीज़ में इस कार्यक्षमता का उपयोग करने के लिए पास करें।
जारेथेकबॉब्स

126

प्रोटो 3 में, सभी फ़ील्ड "वैकल्पिक" हैं (इसमें यदि प्रेषक उन्हें सेट करने में विफल रहता है तो यह त्रुटि नहीं है)। लेकिन, फ़ील्ड अब "शून्य" नहीं हैं, ऐसे में फ़ील्ड के बीच अंतर को स्पष्ट रूप से इसके डिफ़ॉल्ट मान पर सेट करने के लिए कोई अंतर बताने का कोई तरीका नहीं है।

यदि आपको "शून्य" स्थिति की आवश्यकता है (और इसके लिए कोई आउट-ऑफ-रेंज मान नहीं है जो आप इसके लिए उपयोग कर सकते हैं) तो आपको इसके बजाय एक अलग फ़ील्ड के रूप में एन्कोड करने की आवश्यकता होगी। उदाहरण के लिए, आप कर सकते हैं:

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

वैकल्पिक रूप से, आप उपयोग कर सकते हैं oneof:

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

oneofसंस्करण अधिक स्पष्ट और तार के बारे में अधिक कुशल है, लेकिन कैसे समझ की आवश्यकता है oneofमानों काम करते हैं।

अंत में, एक और पूरी तरह से उचित विकल्प है प्रोटो 2 के साथ रहना। Proto2 को पदावनत नहीं किया जाता है, और वास्तव में कई परियोजनाएं (Google के अंदर सहित) बहुत कुछ प्रोटो 2 सुविधाओं पर निर्भर करती हैं जो कि प्रोटो 3 में हटा दी जाती हैं, इसलिए वे संभवतः स्विच नहीं करेंगे। इसलिए, भविष्य के लिए इसका उपयोग करना सुरक्षित है।


आपके समाधान के समान, मेरी टिप्पणी में, मैंने वास्तविक मूल्य और अशक्त प्रकार (एक खाली संदेश) के साथ एक का उपयोग करने का प्रस्ताव दिया। इस तरह आप बूलियन मान से परेशान नहीं होते (जो प्रासंगिक नहीं होना चाहिए, क्योंकि अगर बूलियन है, तो कोई बाज़ नहीं है) सही है?
मैक्सप

2
@MaxP आपका समाधान काम करता है लेकिन मैं एक खाली संदेश पर एक बूलियन की सिफारिश करूंगा। या तो तार पर दो बाइट्स लेंगे, लेकिन खाली संदेश को संभालने के लिए काफी अधिक सीपीयू, रैम, और उत्पन्न कोड ब्लोट लगेगा।
केंटन वर्दा

13
मुझे संदेश फू {oneof baz {int32 baz_value = 1; }} बहुत अच्छी तरह से काम करता है।
साइबरसोनॉपी

@CyberSnoopy क्या आप इसे उत्तर के रूप में पोस्ट कर सकते हैं? आपका समाधान सही और सुरुचिपूर्ण काम करता है।
चेंग चेन

@CyberSnoopy क्या आपने संयोग से किसी भी मुद्दे में भाग लिया है जब प्रतिक्रिया संदेश भेजा जाता है जो कुछ संरचित होता है जैसे: संदेश FooList {दोहराया फू फूस = 1; }? आपका समाधान बहुत अच्छा है लेकिन मुझे सर्वर प्रतिक्रिया के रूप में फूएलस्ट भेजने में अब परेशानी हो रही है।
CaffeinateOften

102

optionalस्वीकृत उत्तर में वर्णित एक तरीका पसंद है: https://stackoverflow.com/a/62566052/1803821

एक और एक आवरण वस्तुओं का उपयोग करना है। आपको उन्हें स्वयं लिखने की आवश्यकता नहीं है क्योंकि Google उन्हें पहले से ही प्रदान करता है:

अपनी .proto फ़ाइल के शीर्ष पर यह आयात जोड़ें:

import "google/protobuf/wrappers.proto";

अब आप प्रत्येक सरल प्रकार के लिए विशेष रैपर का उपयोग कर सकते हैं:

DoubleValue
FloatValue
Int64Value
UInt64Value
Int32Value
UInt32Value
BoolValue
StringValue
BytesValue

तो मूल प्रश्न का उत्तर देने के लिए इस तरह के रैपर का उपयोग इस तरह हो सकता है:

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

अब उदाहरण के लिए जावा में मैं सामान कर सकता हूं जैसे:

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


3
यह कैसे काम करता है? जब baz=nullऔर जब bazपारित नहीं किया जाता है, तो दोनों मामले hasBaz()कहते हैं false!
मयंकपदीक्षित

1
विचार सरल है: आप रैपर ऑब्जेक्ट्स या दूसरे शब्दों में उपयोगकर्ता परिभाषित प्रकारों का उपयोग करते हैं। इन आवरण वस्तुओं को गायब होने की अनुमति है। जावा उदाहरण मैंने प्रदान किया जब gRPC के साथ काम करते हुए मेरे लिए अच्छा काम किया।
वीएम ४

हाँ! मैं सामान्य विचार को समझता हूं, लेकिन मैं इसे कार्रवाई में देखना चाहता था। मुझे समझ में नहीं आता है: (यहां तक ​​कि आवरण की वस्तु में भी) " लापता और अशक्त आवरण मूल्यों की पहचान कैसे करें? "
mayankcpdixit

3
जाने का यह रास्ता है। C # के साथ, उत्पन्न कोड Nullable <T> गुण पैदा करता है।
आरोन हडोन

6
मूल awsner से बेहतर!
देव अग्रवाल

33

केंटन के जवाब के आधार पर, एक सरल अभी तक काम कर समाधान जैसा दिखता है:

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

यह वैकल्पिक-चरित्र को कैसे मूर्त करता है?
JFFIGK

20
मूल रूप से, एक का नाम खराब है। इसका अर्थ है "अधिक से अधिक एक"। वहाँ हमेशा एक संभव अशक्त मूल्य है।
ecl3ctic

यदि छोड़ दिया गया है तो मान का मामला होगा None(C # में) - अपनी पसंद की भाषा के लिए enum-type देखें।
१६:११ बजे १६:११

हां, यह शायद प्रोटॉ 3 में इस बिल्ली की त्वचा का सबसे अच्छा तरीका है - भले ही यह .proto को थोड़ा बदसूरत बना दे।
jschultz410

हालाँकि, यह कुछ हद तक स्पष्ट करता है कि आप एक क्षेत्र की अनुपस्थिति की व्याख्या कर सकते हैं क्योंकि इसे स्पष्ट रूप से शून्य मान पर सेट किया जा सकता है। दूसरे शब्दों में, 'वैकल्पिक क्षेत्र निर्दिष्ट नहीं' के बीच कुछ अस्पष्टता है और 'फ़ील्ड को जानबूझकर निर्दिष्ट नहीं किया गया है जिसका अर्थ है कि यह अशक्त है'। यदि आप सटीकता के उस स्तर के बारे में परवाह करते हैं, तो आप एक अतिरिक्त google.protobuf.NullValue फ़ील्ड को उस ओफ़्फ़ में जोड़ सकते हैं जो आपको 'फ़ील्ड निर्दिष्ट नहीं', 'मान X के रूप में निर्दिष्ट फ़ील्ड' और 'शून्य से निर्दिष्ट फ़ील्ड' के बीच अंतर करने की अनुमति देता है । यह एक तरह से भयंकर है, लेकिन ऐसा इसलिए है क्योंकि प्रोटोन 3 सीधे तौर पर जल्स का समर्थन नहीं करता है।
jschultz410 17

7

@Cybersnoopy के सुझाव पर विस्तार करने के लिए यहाँ

यदि आपके पास एक .proto फ़ाइल है जैसे कोई संदेश:

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

आप प्रदान किए गए मामले के विकल्पों का उपयोग कर सकते हैं (जावा उत्पन्न कोड) :

तो अब हम कुछ कोड इस प्रकार लिख सकते हैं:

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

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

पायथन में यह और भी सरल है। आप बस अनुरोध कर सकते हैं। हाफ़फील्ड ("option_value")। इसके अलावा, यदि आपके पास अपने संदेश के अंदर सिंगलटन सिंगल का एक गुच्छा है, तो आप एक सामान्य स्केलर की तरह सीधे उनके निहित स्केलर तक पहुंच सकते हैं।
jschultz410

1

इस बारे में एक अच्छी पोस्ट है: https://itnext.io/protobuf-and-null-support-1908a15311b6

समाधान आपके वास्तविक उपयोग के मामले पर निर्भर करता है:


LInk के लिए धन्यवाद: itnext.io/protobuf-and-null-support-1908a15311b6 वास्तव में उपयोगी है।
abhilash_goyal

1

संदेश को एन्कोड करने का दूसरा तरीका "सेट" फ़ील्ड को ट्रैक करने के लिए एक और फ़ील्ड जोड़ना है:

syntax="proto3";

package qtprotobuf.examples;

message SparseMessage {
    repeated uint32 fieldsUsed = 1;
    bool   attendedParty = 2;
    uint32 numberOfKids  = 3;
    string nickName      = 4;
}

message ExplicitMessage {
    enum PARTY_STATUS {ATTENDED=0; DIDNT_ATTEND=1; DIDNT_ASK=2;};
    PARTY_STATUS attendedParty = 1;
    bool   indicatedKids = 2;
    uint32 numberOfKids  = 3;
    enum NO_NICK_STATUS {HAS_NO_NICKNAME=0; WOULD_NOT_ADMIT_TO_HAVING_HAD_NICKNAME=1;};
    NO_NICK_STATUS noNickStatus = 4;
    string nickName      = 5;
}

यह विशेष रूप से उपयुक्त है यदि बड़ी संख्या में फ़ील्ड हैं और उनमें से केवल एक छोटी संख्या असाइन की गई है।

अजगर में, उपयोग इस तरह दिखेगा:

import field_enum_example_pb2
m = field_enum_example_pb2.SparseMessage()
m.attendedParty = True
m.fieldsUsed.append(field_enum_example_pb2.SparseMessages.ATTENDEDPARTY_FIELD_NUMBER)

-1

दूसरा तरीका यह है कि आप प्रत्येक वैकल्पिक क्षेत्र के लिए बिटमास्क का उपयोग कर सकते हैं। और उन बिट्स को सेट करें यदि मान सेट हैं और उन बिट्स को रीसेट करें जो मान सेट नहीं हैं

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

बिटमास्क के मूल्य के लिए पार्सिंग चेक पर।

if (bitMask & baz_present)
    baz is present

if (bitMask & baz1_present)
    baz1 is present

-2

यदि आप डिफ़ॉल्ट उदाहरण के साथ संदर्भों की तुलना करके आरंभ किया गया है, तो आप पा सकते हैं:

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

1
यह एक अच्छा सामान्य दृष्टिकोण नहीं है क्योंकि बहुत बार डिफ़ॉल्ट मान फ़ील्ड के लिए एक पूरी तरह से स्वीकार्य मूल्य होता है और उस स्थिति में आप "फ़ील्ड अनुपस्थित" और "फ़ील्ड वर्तमान लेकिन डिफ़ॉल्ट के लिए सेट" के बीच अंतर नहीं कर सकते हैं।
jschultz410
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.