Protobuf 3'te isteğe bağlı bir alan nasıl tanımlanır


105

Protobuf'ta (proto3 sözdizimi) isteğe bağlı bir alana sahip bir mesaj belirtmem gerekiyor. Proto 2 sözdizimi açısından, ifade etmek istediğim mesaj şöyle bir şeydir:

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

Anladığım kadarıyla "isteğe bağlı" kavramı sözdizimi proto 3'ten çıkarıldı (gerekli kavramla birlikte). Alternatif açık olmasa da - göndericiden bir alanın belirtilmediğini belirtmek için varsayılan değeri kullanmak, varsayılan değer geçerli değerler etki alanına aitse bir belirsizlik bırakır (örneğin, bir boole türünü düşünün).

Öyleyse, yukarıdaki mesajı nasıl kodlayacağım? Teşekkür ederim.


Aşağıdaki yaklaşım sağlam bir çözüm mü? mesaj NoBaz {} mesaj Foo {int32 bar = 1; oneof baz {NoBaz undefined = 2; int32 tanımlı = 3; }; }
MaxP

2
Orada bu sorunun bir Proto 2 versiyonu başkaları bu bulmak ama Proto 2. kullanıyorsanız,
chwarr

1
proto3 temelde tüm alanları isteğe bağlı hale getirir. Bununla birlikte, skalarlar için, "alan ayarlanmadı" ve "alan ayarlandı, ancak varsayılan değer" arasında ayrım yapılmasını imkansız hale getirdiler. Skalerinizi örneğin tekli bir şekilde sararsanız - mesaj blah {oneof v1 {int32 foo = 1; }}, daha sonra foo'nun gerçekten ayarlanmış olup olmadığını tekrar kontrol edebilirsiniz. En azından Python için, sanki oneof içinde değilmiş gibi doğrudan foo üzerinde çalışabilir ve HasField'e ("foo") sorabilirsiniz.
jschultz410

1
@MaxP Belki de kabul edilen cevabı stackoverflow.com/a/62566052/66465'e değiştirebilirsiniz çünkü protobuf 3'ün daha yeni bir sürümü artıkoptional
SebastianK,

Yanıtlar:


40

Protobuf sürüm 3.12'den bu yana , proto3, optionalskaler alan varlığı bilgisi vermek için anahtar sözcüğü (proto2'de olduğu gibi) kullanmayı desteklemektedir .

syntax = "proto3";

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

Yukarıdaki alan için proto2'de olduğu gibi bir has_baz()/ hasBaz()yöntem oluşturulur optional.

Protoc , CyberSnoopy'nin cevabının önerdiği gibi, bir optionalalanı bir oneofsarmalayıcı kullanılarak bildirilmiş gibi etkili bir şekilde ele alır :

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

Zaten bu yaklaşımı kullandıysanız, mesajınızın bildirimleri (dan geçiş temizleyebilir oneofiçin optionaltel formatı aynı olduğu,).

Alan varlığı ve optionalproto3 ile ilgili nitty -cesur ayrıntıları Uygulama notu: Saha varlığı belgesinde bulabilirsiniz .

3.12 sürümünde, bu işlevsellik, --experimental_allow_proto3_optionalbayrağın protoc'a aktarılmasını gerektirir . Özellik duyuru o “3.13 yılında inşallah genel olarak mevcut” olacağını söylüyor.

Ekim 2020 Güncellemesi: Bu özellik, 3.13 sürümünde hala deneysel olarak kabul edilmektedir (işaret gereklidir) .


1
C # 'da bayrağı nasıl geçireceğinizi biliyor musunuz?
James Hancock

Bu, proto3'ün daha iyi sözdizimi eklediğine göre en iyi cevap. Harika belirtme çizgisi Jarad!
Evan Moran

Sadece eklemek için optional int xyz: 1) has_xyzisteğe bağlı değerin ayarlanıp ayarlanmadığını algılar 2) clear_xyzdeğeri sıfırlar. Daha fazla bilgi burada: github.com/protocolbuffers/protobuf/blob/master/docs/…
Evan Moran

@JamesHancock veya Java?
Tobi Akinyemi

@TobiAkinyemi ??
James Hancock

123

Proto3'te tüm alanlar "isteğe bağlıdır" (yani gönderenin bunları ayarlamaması bir hata değildir). Ancak alanlar artık "boş değer atanabilir" değildir, çünkü bir alanın açıkça varsayılan değerine ayarlanması ile hiç ayarlanmaması arasındaki farkı anlamanın bir yolu yoktur.

Bir "boş" durumuna ihtiyacınız varsa (ve bunun için kullanabileceğiniz aralık dışı değer yoksa), bunun yerine bunu ayrı bir alan olarak kodlamanız gerekecektir. Örneğin şunları yapabilirsiniz:

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

Alternatif olarak şunları kullanabilirsiniz oneof:

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

oneofVersiyon teline daha açık ve daha verimlidir ama nasıl anlamak gerekir oneofdeğerler çalışır.

Son olarak, bir diğer makul seçenek de proto2'ye bağlı kalmaktır. Proto2 kullanımdan kaldırılmamıştır ve aslında birçok proje (Google içinde dahil), proto3'te kaldırılan proto2 özelliklerine çok bağlıdır, bu nedenle muhtemelen hiçbir zaman geçiş yapmazlar. Bu nedenle, öngörülebilir gelecekte kullanmaya devam etmek güvenlidir.


Çözümünüze benzer şekilde, yorumumda, oneof'u gerçek değer ve boş bir tür (boş bir mesaj) ile kullanmayı önerdim. Böylelikle boolean değerle uğraşmazsınız (bu alakalı olmamalıdır, çünkü boolean varsa baz_value yoktur) Doğru mu?
MaxP

2
@MaxP Çözümünüz çalışıyor, ancak boş bir mesaj yerine bir boole tavsiye ederim. Her ikisi de kabloda iki bayt alacak, ancak boş mesajın işlenmesi için önemli ölçüde daha fazla CPU, RAM ve oluşturulan kod bloğu gerekecek.
Kenton Varda

13
Foo {oneof baz {int32 baz_value = 1; }} oldukça iyi çalışıyor.
CyberSnoopy

@CyberSnoopy Bir cevap olarak gönderebilir misiniz? Çözümünüz mükemmel ve zarif çalışıyor.
Cheng Chen

@CyberSnoopy Şuna benzer şekilde yapılandırılmış bir yanıt mesajı gönderirken şans eseri herhangi bir sorunla karşılaştınız mı? Message FooList {tekrarlanan Foo foos = 1; }? Çözümünüz harika ama şu anda sunucu yanıtı olarak FooList'i göndermede sorun yaşıyorum.
CaffeinateOften

95

Bunun bir yolu, oneofkabul edilen cevapta önerildiği gibi kullanmaktır .

Bir diğeri ise sarmalayıcı nesneler kullanmaktır. Google zaten sağladığı için bunları kendiniz yazmanıza gerek yok:

.Proto dosyanızın en üstüne bu içe aktarmayı ekleyin:

import "google/protobuf/wrappers.proto";

Artık her basit tür için özel sarmalayıcılar kullanabilirsiniz:

DoubleValue
FloatValue
Int64Value
UInt64Value
Int32Value
UInt32Value
BoolValue
StringValue
BytesValue

Dolayısıyla, orijinal soruyu yanıtlamak için böyle bir paketleyicinin kullanımı şöyle olabilir:

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

Şimdi örneğin Java'da şöyle şeyler yapabilirim:

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


3
Bu nasıl çalışıyor? Her iki durumda da baz=nullne zaman ve ne zaman bazgeçilmez hasBaz()diyor false!
mayankcpdixit

Fikir basit: sarmalayıcı nesneleri veya başka bir deyişle kullanıcı tanımlı türleri kullanırsınız. Bu sarmalayıcı nesnelerin eksik olmasına izin verilir. Sağladığım Java örneği, gRPC ile çalışırken benim için iyi çalıştı.
VM4

Evet! Genel fikri anlıyorum ama eylem halinde görmek istedim. Anlamadığım şey: (sarmalayıcı nesnesinde bile) " Eksik ve boş sarmalayıcı değerleri nasıl belirlenir? "
mayankcpdixit

2
Gitmenin yolu bu. C # ile üretilen kod, Nullable <T> özellikleri üretir.
Aaron Hudon

5
Orijinal awsner'dan daha iyi!
Dev Aggarwal

32

Kenton'ın cevabına göre, daha basit ancak çalışan bir çözüm şuna benzer:

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

bu isteğe bağlı karakteri nasıl somutlaştırır?
JFFIGK

19
Temel olarak, biri kötü bir şekilde adlandırılmıştır. "En fazla biri" anlamına gelir. Her zaman olası bir boş değer vardır.
ecl3ctic

Ayarlanmamış bırakılırsa, değer durumu None(C # cinsinden) olacaktır - seçtiğiniz dil için enum türüne bakın.
nitzel

Evet, bu muhtemelen proto3'te bu kedinin derisini yüzmenin en iyi yoludur - .proto'yu biraz çirkinleştirse bile.
jschultz410

Ancak, bir şekilde bir alanın yokluğunu, onu açıkça boş değere ayarlıyor olarak yorumlayabileceğinizi ima eder. Başka bir deyişle, 'isteğe bağlı alan belirtilmedi' ve 'alan, boş olduğu anlamına gelmek üzere kasıtlı olarak belirtilmedi' arasında bir miktar belirsizlik vardır. Bu hassasiyet düzeyini önemsiyorsanız, "alan belirtilmedi", "X değeri olarak belirtilen alan" ve "boş olarak belirtilen alan" arasında ayrım yapmanıza olanak tanıyan ek bir google.protobuf.NullValue alanı ekleyebilirsiniz. . Bu biraz çirkin, ancak bunun nedeni proto3'ün JSON'un yaptığı gibi doğrudan null'u desteklememesi.
jschultz410

7

@Cybersnoopy'nin önerisini burada genişletmek için

böyle bir mesaj içeren bir .proto dosyanız varsa:

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

Sağlanan vaka seçeneklerinden (java tarafından oluşturulan kod) yararlanabilirsiniz :

Şimdi aşağıdaki gibi bir kod yazabiliriz:

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

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

Python'da daha da basit. Sadece request.HasField ("option_value") yapabilirsiniz. Ayrıca, mesajınızın içinde buna benzer bir grup tekil varsa, içerdikleri skalere tıpkı normal bir skaler gibi doğrudan erişebilirsiniz.
jschultz410


-1

Diğer bir yol da, her isteğe bağlı alan için bit maskesi kullanabilmenizdir. ve değerler ayarlanmışsa bu bitleri ayarlayın ve değerlerin ayarlanmadığı bitleri sıfırlayın

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

Ayrıştırmada bitMask'ın değerini kontrol edin.

if (bitMask & baz_present)
    baz is present

if (bitMask & baz1_present)
    baz1 is present

-2

referansları varsayılan örnekle karşılaştırarak birinin başlatılıp başlatılmadığını bulabilirsiniz:

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

1
Bu iyi bir genel yaklaşım değildir çünkü çoğu zaman varsayılan değer alan için tamamen kabul edilebilir bir değerdir ve bu durumda "alan yok" ve "alan mevcut ancak varsayılan olarak ayarlanmış" arasında ayrım yapamazsınız.
jschultz410
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.