checkPortable function

void checkPortable(
  1. Object? value,
  2. String param, [
  3. int depth = 0
])

Reject a JSON value that jsonDecode/jsonEncode accept but serde_json on the Rust side does not. Divergences (all three verified against serde_json 1.0.151):

  • Unpaired surrogate. A Dart String is UTF-16 and may hold one; jsonDecode accepts it both raw and as a \uD800 escape, and jsonEncode writes it back out as a \uD800 escape. serde_json rejects that escape (LoneLeadingSurrogateInHexEscape / UnexpectedEndOfHexEscape).
  • Non-finite number. Dart parses 1e999 to Infinity; serde_json rejects it (NumberOutOfRange).
  • Nesting past _maxJsonDepth.

All three classify as Category::Syntax, which aimux-ffi reports as FfiError::InvalidWireJson (code 202), so it reaches Dart through _ffiError as an invariant StateError — but none of it is an invariant: text.substring(0, n) cutting an emoji in half is enough to produce the first. Catch them here, where the parameter still has a name and the failure is a catchable FormatException.

Implementation

void checkPortable(Object? value, String param, [int depth = 0]) {
  if (depth >= _maxJsonDepth) {
    throw FormatException(
        '$param: JSON nested deeper than $_maxJsonDepth levels', param);
  }
  if (value is String) {
    _checkSurrogates(value, param);
  } else if (value is double && !value.isFinite) {
    throw FormatException('$param: number out of range: $value', param);
  } else if (value is List) {
    for (final element in value) {
      checkPortable(element, param, depth + 1);
    }
  } else if (value is Map) {
    value.forEach((key, element) {
      checkPortable(key, param, depth + 1);
      checkPortable(element, param, depth + 1);
    });
  }
}