protobuf 3에서 선택적 필드를 정의하는 방법


111

protobuf (proto3 구문)에 선택적 필드가있는 메시지를 지정해야합니다. proto 2 구문 측면에서 표현하고 싶은 메시지는 다음과 같습니다.

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

내 이해에서 "선택적"개념은 구문 proto 3에서 제거되었습니다 (필수 개념과 함께). 대안은 명확하지 않지만 기본값을 사용하여 보낸 사람이 필드를 지정하지 않았 음을 나타내면 기본값이 유효한 값 도메인 (예 : 부울 유형을 고려)에 속하는 경우 모호함이 남습니다.

그렇다면 위의 메시지를 어떻게 인코딩해야합니까? 감사합니다.


아래의 접근 방식이 건전한 솔루션입니까? message NoBaz {} message Foo {int32 bar = 1; oneof baz {NoBaz undefined = 2; int32 정의 = 3; }; }
MaxP 2017 년

2
거기에 이 질문의 프로토 2 버전은 다른 사람들이 찾을 프로토 2를 사용하는 경우,
chwarr

1
proto3은 기본적으로 모든 필드를 선택적으로 만듭니다. 그러나 스칼라의 경우 "필드가 설정되지 않음"과 "필드가 설정되었지만 기본값으로"를 구분하는 것이 불가능했습니다. 스칼라를 싱글 톤 oneof 예를 들어 랩핑하는 경우-message blah {oneof v1 {int32 foo = 1; }} 그런 다음 foo가 실제로 설정되었는지 여부를 다시 확인할 수 있습니다. 적어도 파이썬의 경우, 마치 oneof 안에없는 것처럼 foo에서 직접 작업 할 수 있으며 HasField ( "foo")를 요청할 수 있습니다.
jschultz410

1
@MaxP 아마도 당신 은 protobuf 3의 새로운 버전이 현재 가지고 있기 때문에 stackoverflow.com/a/62566052/66465에 대한 대답을 바꿀 수있을 것입니다optional
SebastianK

답변:


55

protobuf 릴리스 3.12 이후 , proto3은 optional스칼라 필드 존재 정보를 제공하기 위해 키워드 (proto2에서와 마찬가지로)를 사용하는 실험적 지원 을 제공합니다.

syntax = "proto3";

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

has_baz()/의 hasBaz()방법은에 대해 생성되는 optional것이 proto2에 있었던 것과 같이, 상기 필드.

내부적으로 protoc 은 CyberSnoopy의 답변에서 알 수 있듯이 optional필드를 oneof래퍼를 사용하여 선언 된 것처럼 효과적으로 처리 합니다.

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

이미 해당 접근 방식을 사용 했다면 proto3 가 실험 상태를 졸업 한 후 연결 형식이 동일하기 때문에 메시지 선언을 정리 (에서 oneof로 전환 optional) 할 수 있습니다 optional.

필드 프레즌스 및 optionalproto3에 대한 핵심 세부 사항은 애플리케이션 노트 : 필드 프레즌스 문서에서 확인할 수 있습니다.

--experimental_allow_proto3_optional릴리스 3.12에서이 기능을 사용 하려면 플래그를 protoc에 전달하십시오. 기능 발표 는 "3.13에 희망 일반적으로 사용"할 것이라고 말했습니다.

2020 년 11 월 업데이트 :이 기능은 릴리스 3.14 에서 아직 실험적 (플래그 필요)으로 간주됩니다 . 진행중인 징후 가 있습니다 .


3
C #에서 플래그를 전달하는 방법을 알고 있습니까?
James Hancock

proto3가 더 나은 구문을 추가했기 때문에 이것이 가장 좋은 대답입니다. 훌륭한 설명 선 Jarad!
Evan Moran

추가하기 위해 optional int xyz: 1) has_xyz옵션 값이 설정되었는지 감지합니다. 2) 값을 설정 clear_xyz해제합니다. 여기에 더 많은 정보 : github.com/protocolbuffers/protobuf/blob/master/docs/...
에반 모란

@JamesHancock 또는 Java?
Tobi Akinyemi

1
@ JónásBalázs 릴리스 3.12에서이 기능을 사용하려면 --experimental_allow_proto3_optional 플래그를 protoc에 전달합니다.
jaredjacobs

127

proto3에서 모든 필드는 "선택 사항"입니다 (발신자가 설정에 실패해도 오류가 아님). 그러나 필드는 더 이상 "nullable"이 아닙니다. 필드가 명시 적으로 기본값으로 설정된 것과 전혀 설정되지 않은 것 사이의 차이를 구분할 방법이 없기 때문입니다.

"null"상태가 필요하고이를 위해 사용할 수있는 범위를 벗어난 값이없는 경우 대신이를 별도의 필드로 인코딩해야합니다. 예를 들어 다음과 같이 할 수 있습니다.

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값이 작동합니다.

마지막으로 완벽하게 합리적인 또 다른 옵션은 proto2를 고수하는 것입니다. Proto2는 더 이상 사용되지 않으며 실제로 많은 프로젝트 (Google 내부 포함)가 proto3에서 제거 된 proto2 기능에 크게 의존하므로 전환되지 않을 가능성이 높습니다. 따라서 가까운 미래에 계속 사용하는 것이 안전합니다.


귀하의 솔루션과 유사하게, 내 의견에서 oneof를 실제 값과 null 유형 (빈 메시지)과 함께 사용하도록 제안했습니다. 이렇게하면 부울 값을 사용하지 않아도됩니다 (부울 값이 있으면 baz_value가 없기 때문에 관련이 없어야합니다). 맞습니까?
MaxP 2017 년

2
@MaxP 귀하의 솔루션은 작동하지만 빈 메시지에 부울을 권장합니다. 어느 쪽이든 와이어에서 2 바이트를 차지하지만 빈 메시지는 처리하는 데 훨씬 더 많은 CPU, RAM 및 생성 된 코드 부풀림이 필요합니다.
Kenton Varda

13
Foo {oneof baz {int32 baz_value = 1; }} 꽤 잘 작동합니다.
CyberSnoopy

@CyberSnoopy 답변으로 게시 할 수 있습니까? 귀하의 솔루션은 완벽하고 우아하게 작동합니다.
Cheng Chen

@CyberSnoopy 다음과 같이 구조화 된 응답 메시지를 보낼 때 우연히 문제가 발생 했습니까? message FooList {repeat Foo foos = 1; }? 귀하의 솔루션은 훌륭하지만 지금 FooList를 서버 응답으로 보내는 데 문제가 있습니다.
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;
}

이제 예를 들어 Java에서 다음과 같은 작업을 수행 할 수 있습니다.

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


3
어떻게 작동합니까? 때 baz=nullbaz전달되지, 두 경우 모두는 hasBaz()말한다 false!
mayankcpdixit

1
아이디어는 간단합니다. 래퍼 객체 또는 다른 말로 사용자 정의 유형을 사용합니다. 이러한 래퍼 개체는 누락 될 수 있습니다. 내가 제공 한 Java 예제는 gRPC로 작업 할 때 잘 작동했습니다.
VM4

네! 나는 일반적인 생각을 이해하지만 실제로 그것을보고 싶었습니다. 내가 이해하지 못하는 것은 (심지어 래퍼 객체) " 어떻게 누락 널 래퍼 값을 식별하는 데? "
mayankcpdixit

3
이것이 갈 길이다. C #을 사용하면 생성 된 코드가 Nullable <T> 속성을 ​​생성합니다.
아론 Hudon

6
원래 awsner보다 낫습니다!
Dev Aggarwal 2011

33

Kenton의 답변에 따르면 더 간단하면서도 작동하는 솔루션은 다음과 같습니다.

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

이것이 선택적 문자를 어떻게 구현합니까?
JFFIGK

20
기본적으로 oneof는 이름이 잘못되었습니다. "최대 하나"를 의미합니다. 항상 가능한 null 값이 있습니다.
ecl3ctic

설정되지 않은 경우 값 대소 문자는 None(C #에서)-선택한 언어의 열거 형 유형을 참조하십시오.
nitzel

예, 이것은 아마도 proto3에서이 고양이를 피부로 만드는 가장 좋은 방법 일 것입니다.
jschultz410

그러나 필드가 없다는 것을 명시 적으로 null 값으로 설정하는 것으로 해석 할 수 있음을 암시합니다. 즉, '옵션 필드가 지정되지 않음'과 '필드가 null임을 의미하기 위해 의도적으로 지정되지 않았습니다'사이에 약간의 모호성이 있습니다. 이 정밀도 수준에 관심이 있다면 '지정되지 않은 필드', '값 X로 지정된 필드'및 '널로 지정된 필드'를 구분할 수있는 추가 google.protobuf.NullValue 필드를 추가 할 수 있습니다. . 다소 흐릿하지만 proto3는 JSON처럼 null을 직접 지원하지 않기 때문입니다.
jschultz410

7

@cybersnoopy의 제안을 여기 에서 확장하려면

다음과 같은 메시지가있는 .proto 파일이있는 경우 :

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

제공된 케이스 옵션 (Java 생성 코드) 을 사용할 수 있습니다 .

이제 다음과 같은 코드를 작성할 수 있습니다.

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

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

Python에서는 훨씬 더 간단합니다. request.HasField ( "option_value") 만 수행 할 수 있습니다. 또한 메시지 내에 이와 같은 단일 항목이 여러 개 있으면 일반 스칼라처럼 포함 된 스칼라에 직접 액세스 할 수 있습니다.
jschultz410


1

의도 한 메시지를 인코딩하는 또 다른 방법은 "set"필드를 추적하기 위해 다른 필드를 추가하는 것입니다.

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

구문 분석시 bitMask 값을 확인하십시오.

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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.