marshal function
Marshals a message (message) to protobuf binary bytes using descriptor.
Iterates over each field descriptor, looks up the corresponding value in
message by name, and encodes it according to its type and label.
Returns the encoded protobuf bytes as a List<int>.
Implementation
List<int> marshal(
Map<String, Object?> message,
List<Map<String, Object?>> descriptor,
) {
final buffer = <int>[];
for (final field in descriptor) {
final name = field['name'] as String;
final fieldNumber = field['number'] as int;
final type = field['type'] as String;
final label = field['label'] as String?;
final features = field['features'] as Map<String, String>?;
final value = message[name];
// Skip null values — field not present in the message map.
if (value == null) continue;
// Map fields: descriptor carries keyType/valueType metadata. Accept any
// `Map` — binary unmarshal yields native-typed keys (`Map<Object?,Object?>`
// keyed by e.g. `int`), while the JSON codec yields string keys; both must
// round-trip through here.
if (label == 'LABEL_REPEATED' &&
type == 'TYPE_MESSAGE' &&
value is Map &&
field['mapEntry'] == true) {
final keyType = field['keyType'] as String? ?? 'TYPE_STRING';
final valueType = field['valueType'] as String? ?? 'TYPE_STRING';
// For `map<K, message>` the value sub-message's field descriptors live on
// the map field itself (set by the descriptor bridge); thread them through
// so message-typed values re-marshal instead of failing.
final valueDescriptor =
field['messageDescriptor'] as List<Map<String, Object?>>?;
marshalMapField(
buffer,
fieldNumber,
value,
keyType,
valueType,
valueDescriptor: valueDescriptor,
);
continue;
}
// Repeated fields.
if (label == 'LABEL_REPEATED') {
final list = value as List<Object?>;
if (list.isEmpty) continue;
if (type == 'TYPE_MESSAGE') {
// Repeated messages are never packed — each is a separate record.
// DELIMITED (group) encoding wraps each in START_GROUP/END_GROUP;
// otherwise each is a length-prefixed (LEN) field.
final delimited = features != null && isDelimited(features);
final msgDescriptor =
field['messageDescriptor'] as List<Map<String, Object?>>?;
for (final item in list) {
if (item == null) continue;
final subBytes = msgDescriptor != null
? marshal(item as Map<String, Object?>, msgDescriptor)
: item as List<int>;
if (delimited) {
encodeGroupField(buffer, fieldNumber, subBytes);
} else {
encodeMessageField(buffer, fieldNumber, subBytes);
}
}
} else if (type == 'TYPE_STRING') {
// Repeated strings are not packed — each is a separate LEN field.
for (final item in list) {
if (item == null) continue;
encodeStringField(buffer, fieldNumber, item as String);
}
} else if (type == 'TYPE_BYTES') {
// Repeated bytes are not packed — each is a separate LEN field.
for (final item in list) {
if (item == null) continue;
encodeBytesField(buffer, fieldNumber, item as List<int>);
}
} else if (features != null && isExpandedRepeated(features)) {
// Expanded scalar repeated (proto2 / EXPANDED override): each element
// is its own tag-value record rather than one packed blob. Every
// element is written, including default values (no proto3 elision).
for (final item in list) {
if (item == null) continue;
_writeScalarForced(buffer, fieldNumber, type, item);
}
} else {
// Packed scalar repeated field (proto3 / editions default).
marshalRepeated(buffer, fieldNumber, type, list);
}
continue;
}
// Singular field. With EXPLICIT/LEGACY_REQUIRED presence the value is
// serialized even when it equals the type default (proto3/IMPLICIT elides).
// A DELIMITED message field is emitted as a group (wire type 3/4).
final msgDescriptor =
field['messageDescriptor'] as List<Map<String, Object?>>?;
marshalField(
buffer,
fieldNumber,
type,
value,
explicitPresence: features != null && hasExplicitPresence(features),
delimited: features != null && isDelimited(features),
messageDescriptor: msgDescriptor,
);
}
return buffer;
}