Mainline protoc --decode produces a readable, line-oriented text
representation of any proto message. The pb runtime emits the
same shape via pb.text.encode and parses it via pb.text.decode.
Use it for:
single_line = true and you get a one-liner
that grep / jq won't choke on..txtpb files round-trip into bytes via
mainline protoc --encode and into Lua tables via
pb.text.decode; the conformance suite uses this pattern for the
10-fixture interop corpus.Generated codegen emits a M.<Type>_text wrapper:
local hello = require('full.hello.hello_pb')
print(hello.Person_text({name = 'Alice', age = 30, emails = {'a@x'}}))
-- name: "Alice"
-- age: 30
-- emails: "a@x"
-- Compact, one-line form:
print(hello.Person_text({name = 'Alice', age = 30}, {single_line = true}))
-- name: "Alice" age: 30
-- Custom indent:
print(hello.Person_text(t, {indent = ' '}))
Options:
| Key | Default | Meaning |
|---|---|---|
single_line |
false |
Collapse to a single space-separated line — log-friendly. |
indent |
' ' (two spaces) |
Per-nesting-level indent. Ignored when single_line = true. |
There's no per-message _text_decode wrapper — call
pb.text.decode(desc, text) directly. (The wrapper was deliberately
not generated; it's used in a few places like the conformance
runner, and didn't warrant codegen surface.)
local pb = require('pb')
local t = pb.text.decode(hello.Person_descriptor, [[
name: "Alice"
age: 30
emails: "a@x"
emails: "b@x"
]])
print(t.name, t.age, t.emails[1])
The parser handles every grammar bucket the proto3 conformance text suite exercises:
0x hex, 0-prefix octal.inf / infinity / nan (any case), oversize
exponents saturating to ±inf, underflow to ±0.\n, \xFF, \377) and \u/\U escapes with
adjacent-literal concat.{ ... } and < ... > bodies for sub-messages.field: [a, b, c].field { key: K value: V }.Any: field [type.googleapis.com/Foo] { ... }.status: OK) or number (status: 1).42: "value" works alongside
name: "value".protocThe interop suite leans on this:
# Encode a .txtpb fixture to wire bytes via mainline protoc.
protoc --encode=hello.Person hello.proto < person.txtpb > person.bin
# Decode it back via pb.
local bytes = io.open('person.bin', 'rb'):read('*a')
local p = hello.Person_decode(bytes)
print(hello.Person_text(p))
The text form is what makes the fixtures human-readable in
test/interop/fixtures/*.txtpb while the wire-equality check runs
against the matching .bin files.
%.*g formatting — bit-exact
round-trip is guaranteed (the codec picks precision to hit
IEEE-754 exact reconstruction), but adjacent-fixture byte
equality vs mainline protoc can differ in non-significant
digits.