Oneof Fields
The oneof field allows you to define a union type where only one field can be set at a time. This is useful for representing variant data.
Defining Oneof Types
A oneof is defined as a separate object type with the {.oneof.} pragma, using a Nim case object:
type
ContactKind {.pure.} = enum
notSet
email
phone
address
ContactInfo {.proto3, oneof.} = object
case kind: ContactKind
of ContactKind.notSet:
discard
of ContactKind.email:
email {.fieldNumber: 1.}: string
of ContactKind.phone:
phone {.fieldNumber: 2.}: string
of ContactKind.address:
address {.fieldNumber: 3.}: string
Person {.proto3.} = object
name {.fieldNumber: 10.}: string
contact {.oneof.}: ContactInfo
How Oneof Works
Only one field in a oneof can be set at a time. When you set a field, all other fields are cleared:
# Create a person with email contact
let person1 = Person(
name: "Alice",
contact: ContactInfo(kind: ContactKind.email, email: "alice@example.com")
)
assert person1.contact.kind == ContactKind.email
assert person1.contact.email == "alice@example.com"
# Encode and decode
let encoded = Protobuf.encode(person1)
let decoded = Protobuf.decode(encoded, Person)
assert decoded.name == "Alice"
assert decoded.contact.kind == ContactKind.email
assert decoded.contact.email == "alice@example.com"
# Create a person with phone contact
let person2 = Person(
name: "Bob",
contact: ContactInfo(kind: ContactKind.phone, phone: "+1234567890")
)
assert person2.contact.kind == ContactKind.phone
assert person2.contact.phone == "+1234567890"
Default State
When a oneof is not set, it defaults to the first value of the discriminator enum. In the example above, that's ContactKind.notSet, but you can name it whatever makes sense for your use case:
let person = Person(name: "Bob")
assert person.contact.kind == ContactKind.notSet
let encoded = Protobuf.encode(person)
let decoded = Protobuf.decode(encoded, Person)
assert decoded.contact.kind == ContactKind.notSet
Oneof with Different Types
Oneof fields can have different types:
import protobuf_serialization
type
ValueKind {.pure.} = enum
notSet
intValue
stringValue
boolValue
Value {.proto3, oneof.} = object
case kind: ValueKind
of ValueKind.notSet:
discard
of ValueKind.intValue:
intValue {.fieldNumber: 10, sint.}: int32
of ValueKind.stringValue:
stringValue {.fieldNumber: 11.}: string
of ValueKind.boolValue:
boolValue {.fieldNumber: 12.}: bool
Config {.proto3.} = object
key {.fieldNumber: 1.}: string
value {.oneof.}: Value
# Usage
let config1 = Config(
key: "timeout",
value: Value(kind: ValueKind.intValue, intValue: 30)
)
let config2 = Config(
key: "name",
value: Value(kind: ValueKind.stringValue, stringValue: "Alice")
)
let config3 = Config(
key: "enabled",
value: Value(kind: ValueKind.boolValue, boolValue: true)
)
# Encode and decode
let encoded1 = Protobuf.encode(config1)
let decoded1 = Protobuf.decode(encoded1, Config)
assert decoded1.key == "timeout"
assert decoded1.value.kind == ValueKind.intValue
assert decoded1.value.intValue == 30
echo "Oneof with different types example passed!"
Oneof with Nested Messages
Oneof fields can contain nested message types:
import protobuf_serialization
type
Error {.proto3.} = object
code {.fieldNumber: 1, pint.}: int32
message {.fieldNumber: 2.}: string
Success {.proto3.} = object
data {.fieldNumber: 1.}: string
ResultKind {.pure.} = enum
notSet
success
error
Result {.proto3, oneof.} = object
case kind: ResultKind
of ResultKind.notSet:
discard
of ResultKind.success:
success {.fieldNumber: 10.}: Success
of ResultKind.error:
error {.fieldNumber: 11.}: Error
Response {.proto3.} = object
id {.fieldNumber: 1.}: string
result {.oneof.}: Result
# Usage
let response = Response(
id: "req-123",
result: Result(
kind: ResultKind.success,
success: Success(data: "Operation completed")
)
)
# Encode and decode
let encoded = Protobuf.encode(response)
let decoded = Protobuf.decode(encoded, Response)
assert decoded.id == "req-123"
assert decoded.result.kind == ResultKind.success
assert decoded.result.success.data == "Operation completed"
echo "Oneof with nested messages example passed!"
Next Steps
- Learn about optional fields with
PBOption - Explore type extensions for custom serialization