[protocol-buffers] protobuf 3에서 선택적 필드를 정의하는 방법

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

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

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

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



답변

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 에서 아직 실험적 (플래그 필요)으로 간주됩니다 . 진행중인 징후 가 있습니다 .


답변

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 기능에 크게 의존하므로 전환되지 않을 가능성이 높습니다. 따라서 가까운 미래에 계속 사용하는 것이 안전합니다.


답변

한 가지 방법은 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()) { ... }


답변

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

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


답변

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


답변

이에 대한 좋은 게시물이 있습니다 : https://itnext.io/protobuf-and-null-support-1908a15311b6

솔루션은 실제 사용 사례에 따라 다릅니다.


답변

의도 한 메시지를 인코딩하는 또 다른 방법은 “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)