Skip to content

Implement #712: idiomatic protobuf map<K,V> support (read + write) - #719

Merged
cowtowncoder merged 13 commits into
2.21from
tatu-claude/2.21/712-protobuf-map-support
Aug 12, 2026
Merged

Implement #712: idiomatic protobuf map<K,V> support (read + write)#719
cowtowncoder merged 13 commits into
2.21from
tatu-claude/2.21/712-protobuf-map-support

Conversation

@cowtowncoder

Copy link
Copy Markdown
Member

Maps are encoded on the wire exactly like a repeated entry sub-message (key = tag 1, value = tag 2). This exposes them idiomatically as JSON Objects in both directions, at streaming and databind levels.

Schema (TypeResolver / ProtobufField): a map<K,V> field (or, from a protoc descriptor set, a repeated field whose entry message carries the map_entry option) is resolved into a synthetic entry message plus a repeated, map-flagged field. Key types are restricted to integral / bool / string.

Generator: a map field is written as a sequence of length-delimited entry sub-messages; each writeFieldName opens an entry (key rendered from the JSON field name per the key type) and primes the value field.

Parser: a map surfaces as START_OBJECT + FIELD_NAME(key)/value pairs + END_OBJECT, mirroring the unpacked-repeated-message machinery (with an entry context so end-offset tracking survives buffer reloads). Absent key/value decode to proto3 defaults; a value that precedes the key is reported clearly.

Descriptor path (FileDescriptorSet): propagate the map_entry message option so .desc-loaded maps get the same treatment as .proto ones. NOTE: this changes the shape of .desc-loaded maps, which previously surfaced as an array of {key,value} entries.

Also flips the two #708 tests that asserted maps throw, and updates the now-stale notes in ProtobufSchemaPreprocessor. Schema generation from POJO Map fields (ProtoBufSchemaVisitor.expectMapFormat) remains unsupported; left for a follow-up.

Maps are encoded on the wire exactly like a `repeated` entry sub-message
(key = tag 1, value = tag 2). This exposes them idiomatically as JSON Objects
in both directions, at streaming and databind levels.

Schema (TypeResolver / ProtobufField): a `map<K,V>` field (or, from a protoc
descriptor set, a `repeated` field whose entry message carries the `map_entry`
option) is resolved into a synthetic entry message plus a repeated, map-flagged
field. Key types are restricted to integral / bool / string.

Generator: a map field is written as a sequence of length-delimited entry
sub-messages; each writeFieldName opens an entry (key rendered from the JSON
field name per the key type) and primes the value field.

Parser: a map surfaces as START_OBJECT + FIELD_NAME(key)/value pairs +
END_OBJECT, mirroring the unpacked-repeated-message machinery (with an entry
context so end-offset tracking survives buffer reloads). Absent key/value decode
to proto3 defaults; a value that precedes the key is reported clearly.

Descriptor path (FileDescriptorSet): propagate the `map_entry` message option so
.desc-loaded maps get the same treatment as .proto ones. NOTE: this changes the
shape of .desc-loaded maps, which previously surfaced as an array of
{key,value} entries.

Also flips the two #708 tests that asserted maps throw, and updates the now-stale
notes in ProtobufSchemaPreprocessor. Schema generation from POJO Map fields
(ProtoBufSchemaVisitor.expectMapFormat) remains unsupported; left for a follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
cowtowncoder and others added 12 commits July 25, 2026 09:59
While entries are iterated `_currentMessage` points at the synthetic entry
type. The map exit taken when a different field tag follows restored
`_parsingContext` and `_currentField` but not `_currentMessage`, so the
replayed tag was resolved against the entry type whenever the `next`-field
chain missed -- which it does for any tag gap after the map.

proto3 omits unset fields, so such a gap is ordinary, and a document written
by this module could fail to read back with "Undefined property (id 3, wire
type 2) for message type map<m>". Also fixes a related case where entries
split by another field decoded trailing entry bytes as a bogus `key` field.

Restores it on the EOF exit too, matching what `_checkEnd()` already did.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Existing message-valued map coverage used flat value messages (scalar fields
only). A value message with nesting of its own pushes further context levels
through the same save/restore path the map exit has to unwind -- which is what
a4d66df got wrong, and its regression test covers only the scalar shape.

Six tests, all passing as written; regression guards, not fixes:

- value message holding another message
- value message holding a `repeated` field
- value message declaring its own `map` (one synthetic entry type current while
  another is entered)
- three entries of nested-message + repeated, then a field after the map
- empty value message: pins the wire bytes, showing an empty value still writes
  an entry unlike an empty map
- the a4d66df tag-gap shape with a message-valued map, one level deeper

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A `null` map value has no protobuf encoding, so it writes as a key-only entry;
read back, such an entry yields `null` rather than the empty message protobuf
would consider it. An entry must produce some value token -- unlike a plain
message field, which simply stays absent -- and the choice round-trips, but
nothing pinned it, so either direction could be flipped unnoticed.

Two tests: the message-valued case (both directions, plus the contrast with an
empty value message, which does write `12 00`), and the scalar case, whose read
direction `testAbsentKeyAndValueDefaults` already covers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@cowtowncoder cowtowncoder added this to the 2.21.6 milestone Aug 12, 2026
@cowtowncoder
cowtowncoder merged commit 9f9ee70 into 2.21 Aug 12, 2026
3 checks passed
@cowtowncoder
cowtowncoder deleted the tatu-claude/2.21/712-protobuf-map-support branch August 12, 2026 03:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant