Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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