fieldFromJson function
Converts a Proto3 JSON value back to its protobuf field representation.
Type-specific conversions:
TYPE_INT64,TYPE_UINT64,TYPE_SINT64,TYPE_SFIXED64,TYPE_FIXED64: string -> int.TYPE_BYTES: base64 string ->List<int>.TYPE_FLOAT,TYPE_DOUBLE: "NaN"/"Infinity"/"-Infinity" -> double.TYPE_ENUM: string name -> ordinal using the reverseenumValuesmap.TYPE_MESSAGE: recursively unmarshaled usingmessageDescriptor.TYPE_INT32,TYPE_UINT32, etc.: numeric coercion from JSON number.TYPE_BOOL: passed through.TYPE_STRING: passed through.
enumValues maps ordinal -> name; this function reverses it to look up
by name. messageDescriptor is the nested field descriptor list for
TYPE_MESSAGE fields.
Implementation
Object? fieldFromJson(
Object? jsonValue,
String type, {
String? typeName,
Map<int, String>? enumValues,
Map<String, int>? enumNames,
List<Map<String, Object?>>? messageDescriptor,
Map<String, String>? features,
AnyTypeResolver? resolver,
}) {
if (jsonValue == null) {
// A JSON null on a google.protobuf.Value field is the null *value*
// (null_value), not an absent field.
if (typeName == 'google.protobuf.Value') return {'null_value': 0};
return null;
}
switch (type) {
// 64-bit integers: a JSON string or number (proto3 JSON encodes these as
// strings, but accepts numbers too; exponential and quoted forms allowed).
// Range-checked: signed in [-2^63, 2^63-1], unsigned in [0, 2^64-1].
case 'TYPE_INT64':
case 'TYPE_SINT64':
case 'TYPE_SFIXED64':
return _jsonInt64(jsonValue, signed: true);
case 'TYPE_UINT64':
case 'TYPE_FIXED64':
return _jsonInt64(jsonValue, signed: false);
// bytes: base64 string -> List<int>.
case 'TYPE_BYTES':
if (jsonValue is String) {
return base64.decode(jsonValue);
}
return jsonValue;
// float / double: special string literals, else a finite number in range.
case 'TYPE_FLOAT':
case 'TYPE_DOUBLE':
final double d;
if (jsonValue is String) {
switch (jsonValue) {
case 'NaN':
return double.nan;
case 'Infinity':
return double.infinity;
case '-Infinity':
return double.negativeInfinity;
default:
d = double.parse(jsonValue);
// A finite numeric literal that overflowed to infinity is invalid
// (the explicit "Infinity" forms were handled above).
if (!d.isFinite) {
throw FormatException('Number out of range: "$jsonValue"');
}
}
} else if (jsonValue is num) {
d = jsonValue.toDouble();
if (!d.isFinite) {
throw FormatException('Number out of range: $jsonValue');
}
} else {
throw FormatException(
'Expected a number for float/double, got ${jsonValue.runtimeType}',
);
}
// float (32-bit) magnitude must fit in IEEE-754 single precision.
if (type == 'TYPE_FLOAT' && d.abs() > 3.4028234663852886e38) {
throw FormatException('float field out of range: $d');
}
return d;
// enum: string name -> ordinal.
case 'TYPE_ENUM':
if (jsonValue is String) {
// Name -> number, including allow_alias aliases (enumNames carries every
// spelling; enumValues only the canonical one).
final byName = enumNames?[jsonValue];
if (byName != null) return byName;
if (enumValues != null) {
for (final entry in enumValues.entries) {
if (entry.value == jsonValue) return entry.key;
}
}
// If the string is a numeric literal, parse it.
final numeric = int.tryParse(jsonValue);
if (numeric != null) return numeric;
// Unknown enum name. Proto3 JSON (json_format = ALLOW, the editions
// default) requires a known name or a numeric literal — reject. Under
// LEGACY_BEST_EFFORT (proto2) or when features are absent, tolerate it
// by passing the raw string through (legacy best-effort behavior).
if (features != null && jsonFormatIsAllow(features)) {
throw FormatException(
'Unknown enum value "$jsonValue" (json_format=ALLOW requires a '
'known name or numeric literal)',
);
}
return jsonValue;
}
if (jsonValue is num) {
return jsonValue.toInt();
}
return jsonValue;
// message: recursive unmarshal (or a well-known-type JSON mapping).
case 'TYPE_MESSAGE':
if (typeName == 'google.protobuf.Any' &&
(resolver ?? anyTypeResolver) != null) {
return _anyFromJson(jsonValue, resolver);
}
if (typeName != null && isWellKnownJsonType(typeName)) {
return wktFromJson(typeName, jsonValue, features, resolver);
}
if (messageDescriptor != null) {
if (jsonValue is! Map) {
throw FormatException(
'Expected a JSON object for a message field, '
'got ${jsonValue.runtimeType}',
);
}
final stringMap = <String, Object?>{};
for (final entry in jsonValue.entries) {
stringMap[entry.key.toString()] = entry.value;
}
return _unmarshalFromMap(stringMap, messageDescriptor, resolver);
}
return jsonValue;
// Signed 32-bit integers: range-checked.
case 'TYPE_INT32':
case 'TYPE_SINT32':
case 'TYPE_SFIXED32':
final n = _parseJsonInt(jsonValue);
if (n < -2147483648 || n > 2147483647) {
throw FormatException('int32 field value out of range: $n');
}
return n;
// Unsigned 32-bit integers: range-checked.
case 'TYPE_UINT32':
case 'TYPE_FIXED32':
final n = _parseJsonInt(jsonValue);
if (n < 0 || n > 4294967295) {
throw FormatException('uint32 field value out of range: $n');
}
return n;
case 'TYPE_BOOL':
if (jsonValue is bool) return jsonValue;
if (jsonValue is String) return jsonValue == 'true';
return jsonValue;
case 'TYPE_STRING':
if (jsonValue is! String) {
throw FormatException(
'Expected a JSON string, got ${jsonValue.runtimeType}',
);
}
_verifyUtf8IfRequired(jsonValue, features);
return jsonValue;
default:
return jsonValue;
}
}