fieldFromJson function

Object? fieldFromJson(
  1. Object? jsonValue,
  2. String type, {
  3. String? typeName,
  4. Map<int, String>? enumValues,
  5. Map<String, int>? enumNames,
  6. List<Map<String, Object?>>? messageDescriptor,
  7. Map<String, String>? features,
  8. AnyTypeResolver? resolver,
})

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 reverse enumValues map.
  • TYPE_MESSAGE: recursively unmarshaled using messageDescriptor.
  • 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;
  }
}