Skip to content

AsyncParser final

final class AsyncParser extends Parser

A stateful, incremental JSON parser that accepts data in chunks.

Call absorb / absorbString to feed bytes or string chunks, and finish / finalAbsorb when the last chunk has been delivered. Each call returns Right(IList<Json>) containing any values completed so far, or Left(ParseException) on a syntax error.

The parsing mode is set at construction time via AsyncParserMode:

AsyncParser is used internally by JsonTransformer; prefer that API for stream-based decoding.

Constructors

AsyncParser() factory

factory AsyncParser({
  AsyncParserMode mode = AsyncParserMode.singleValue,
  bool multiValue = false,
})
Implementation
dart
factory AsyncParser({
  AsyncParserMode mode = AsyncParserMode.singleValue,
  bool multiValue = false,
}) => AsyncParser._(
  mode._start,
  0,
  null,
  [],
  List.empty(growable: true),
  0,
  0,
  false,
  mode._value,
  multiValue,
);

Properties

hashCode no setter inherited

int get hashCode

The hash code for this object.

A hash code is a single integer which represents the state of the object that affects operator == comparisons.

All objects have hash codes. The default hash code implemented by Object represents only the identity of the object, the same way as the default operator == implementation only considers objects equal if they are identical (see identityHashCode).

If operator == is overridden to use the object state instead, the hash code must also be changed to represent that state, otherwise the object cannot be used in hash based data structures like the default Set and Map implementations.

Hash codes must be the same for objects that are equal to each other according to operator ==. The hash code of an object should only change if the object changes in a way that affects equality. There are no further requirements for the hash codes. They need not be consistent between executions of the same program and there are no distribution guarantees.

Objects that are not equal are allowed to have the same hash code. It is even technically allowed that all instances have the same hash code, but if clashes happen too often, it may reduce the efficiency of hash-based data structures like HashSet or HashMap.

If a subclass overrides hashCode, it should override the operator == operator as well to maintain consistency.

Inherited from Object.

Implementation
dart
external int get hashCode;

runtimeType no setter inherited

Type get runtimeType

A representation of the runtime type of the object.

Inherited from Object.

Implementation
dart
external Type get runtimeType;

Methods

absorb()

Either<ParseException, IList<Json>> absorb(Uint8List buf)

Feeds buf into the parser and returns any Json values completed so far. Does not signal end-of-input; call finish when done.

Implementation
dart
Either<ParseException, IList<Json>> absorb(Uint8List buf) {
  _done = false;
  _data.addAll(buf);
  _len = _data.length;
  return _churn();
}

absorbString()

Either<ParseException, IList<Json>> absorbString(String buf)

Encodes buf as UTF-8 and feeds it to absorb.

Implementation
dart
Either<ParseException, IList<Json>> absorbString(String buf) => absorb(utf8.encoder.convert(buf));

at()

String at(int i)

Returns the character at position i as a single-character string.

Implementation
dart
@override
String at(int i) => String.fromCharCode(byte(i));

atCodeUnit()

int atCodeUnit(int i)

Returns the UTF-16 code unit at position i.

Implementation
dart
@override
int atCodeUnit(int i) => byte(i);

atEof()

bool atEof(int i)

Returns true if i is at or past the end of the input.

Implementation
dart
@override
bool atEof(int i) => _done && i >= _len;

atRange()

String atRange(int i, int j)

Returns the substring from i (inclusive) to j (exclusive).

Implementation
dart
@override
String atRange(int i, int j) {
  if (j > _len) throw AsyncException();
  final size = j - i;

  final bytes = _data.getRange(i, i + size);
  return String.fromCharCodes(bytes);
}

byte()

int byte(int i)

Returns the raw byte at position i.

Implementation
dart
@override
int byte(int i) {
  if (i >= _len) {
    throw AsyncException();
  } else {
    return _data[i];
  }
}

checkpoint()

void checkpoint(int state, int i, FContext context, List<FContext> stack)

Saves the current parse state so that an async parser can resume if the buffer runs out mid-value.

Implementation
dart
@override
void checkpoint(int state, int i, FContext context, List<FContext> stack) {
  _state = state;
  _curr = i;
  _context = context;
  _stack = List.of(stack);
}

close()

void close()

Called when parsing is finished; subclasses may release resources here.

Implementation
dart
@override
void close() {}

column()

int column(int i)

Returns the 0-based column of position i.

Implementation
dart
@override
int column(int i) => i - _pos;

descape() inherited

String descape(int pos, String s)

Inherited from Parser.

Implementation
dart
String descape(int pos, String s) {
  int i = 0;
  int x = 0;

  while (i < 4) {
    final n = _hexChars[s.codeUnitAt(i)];

    if (n < 0) {
      die(pos, 'expected valid unicode escape');
    }

    x = (x << 4) | n;
    i += 1;
  }

  return String.fromCharCode(x);
}

die() inherited

Never die(int i, String msg, {int chars = _ErrorContext})

Inherited from Parser.

Implementation
dart
Never die(int i, String msg, {int chars = _ErrorContext}) {
  final y = line() + 1;
  final x = column(i) + 1;

  final String got;

  if (atEof(i)) {
    got = 'eof';
  } else {
    int offset = 0;

    while (offset < chars && !atEof(i + offset)) {
      offset += 1;
    }

    final txt = _safeAt(i, i + offset);

    if (atEof(i + offset)) {
      got = "'$txt'";
    } else {
      got = "'$txt...";
    }
  }

  throw ParseException('$msg got $got (line $y, column $x)', i, y, x);
}

finalAbsorb()

Either<ParseException, IList<Json>> finalAbsorb(Uint8List buf)

Feeds buf and signals end-of-input, returning all remaining Json values or a ParseException if the input is incomplete or invalid.

Implementation
dart
Either<ParseException, IList<Json>> finalAbsorb(Uint8List buf) => absorb(buf).fold(
  (err) => err.asLeft(),
  (xs) => finish().fold(
    (err) => err.asLeft(),
    (ys) => xs.concat(ys).asRight(),
  ),
);

finalAbsorbString()

Either<ParseException, IList<Json>> finalAbsorbString(String buf)

Encodes buf as UTF-8 and calls finalAbsorb.

Implementation
dart
Either<ParseException, IList<Json>> finalAbsorbString(String buf) =>
    finalAbsorb(utf8.encoder.convert(buf));

finish()

Either<ParseException, IList<Json>> finish()

Signals end-of-input and flushes any remaining buffered data, returning completed Json values or a ParseException.

Implementation
dart
Either<ParseException, IList<Json>> finish() {
  _done = true;
  return _churn();
}

iparse() inherited

Record iparse(int state, int j, FContext context, List<FContext> stack)

Inherited from Parser.

Implementation
dart
(Json, int) iparse(
  int state,
  int j,
  FContext context,
  List<FContext> stack,
) {
  &#47;&#47; No tail recursion so we must loop
  int iState = state;
  int iJ = j;
  FContext iContext = context;
  final iStack = stack;

  while (true) {
    final i = reset(iJ);
    checkpoint(iState, i, iContext, iStack);

    final c = atCodeUnit(i);

    if (iStack.length > _MaxDepth) {
      die(i, 'JSON max depth ($_MaxDepth) exceeded');
    }

    if (c == 10) {
      &#47;&#47; '\n'
      newline(i);
      iJ = i + 1;
    } else if (c == 32 || c == 9 || c == 13) {
      &#47;&#47; ' ', '\t', '\r'
      iJ = i + 1;
    } else if (iState == _DATA) {
      &#47;&#47; we are inside an object or array expecting to see data
      if (c == 91) {
        &#47;&#47; '['
        iState = _ARRBEG;
        iJ = i + 1;
        iStack.add(iContext);
        iContext = FContext.array(i);
      } else if (c == 123) {
        &#47;&#47; '{'
        iState = _OBJBEG;
        iJ = i + 1;
        iStack.add(iContext);
        iContext = FContext.object(i);
      } else if ((c >= 48 && c <= 57) || c == 45) {
        &#47;&#47; '0'-'9' or '-'
        iJ = _parseNum(i, iContext);
        iState = iContext.isObject ? _OBJEND : _ARREND;
      } else if (c == 34) {
        &#47;&#47; '"'
        iJ = parseString(i, iContext);
        iState = iContext.isObject ? _OBJEND : _ARREND;
      } else if (c == 116) {
        &#47;&#47; 't'
        iContext.addValueAt(_parseTrue(i), i);
        iState = iContext.isObject ? _OBJEND : _ARREND;
        iJ = i + 4;
      } else if (c == 102) {
        &#47;&#47; 'f'
        iContext.addValueAt(_parseFalse(i), i);
        iState = iContext.isObject ? _OBJEND : _ARREND;
        iJ = i + 5;
      } else if (c == 110) {
        &#47;&#47; 'n'
        iContext.addValueAt(_parseNull(i), i);
        iState = iContext.isObject ? _OBJEND : _ARREND;
        iJ = i + 4;
      } else {
        die(i, 'expected json value');
      }
    } else if ((c == 93 && (iState == _ARREND || iState == _ARRBEG)) || &#47;&#47; ']'
        (c == 125 && (iState == _OBJEND || iState == _OBJBEG))) {
      &#47;&#47; '}'
      &#47;&#47; we are inside an array or object and have seen a key or a closing
      &#47;&#47; brace, respectively.
      if (iStack.isEmpty) {
        return (iContext.finishAt(i), i + 1);
      } else {
        final ctxt2 = iStack.removeLast();
        ctxt2.addValueAt(iContext.finishAt(i), i);

        iState = ctxt2.isObject ? _OBJEND : _ARREND;
        iJ = i + 1;
        iContext = ctxt2;
      }
    } else if (iState == _KEY) {
      &#47;&#47; we are in an object expecting to see a key.
      if (c == 34) {
        &#47;&#47; '"'
        iJ = parseString(i, iContext);
        iState = _SEP;
      } else {
        die(i, 'expected "');
      }
    } else if (iState == _SEP) {
      &#47;&#47; we are in an object just after a key, expecting to see a colon.
      if (c == 58) {
        &#47;&#47; ':'
        iState = _DATA;
        iJ = i + 1;
      } else {
        die(i, 'expected :');
      }
    } else if (iState == _ARREND) {
      &#47;&#47; we are in an array, expecting to see a comma (before more data).
      if (c == 44) {
        &#47;&#47; ','
        iState = _DATA;
        iJ = i + 1;
      } else {
        die(i, 'expected ] or ,');
      }
    } else if (iState == _OBJEND) {
      &#47;&#47; we are in an object, expecting to see a comma (before more data).
      if (c == 44) {
        &#47;&#47; ','
        iState = _KEY;
        iJ = i + 1;
      } else {
        die(i, 'expected } or ,');
      }
    } else if (iState == _ARRBEG) {
      &#47;&#47; we are starting an array, expecting to see data or a closing bracket.
      iState = _DATA;
      iJ = i;
    } else {
      &#47;&#47; we are starting an object, expecting to see a key or a closing brace.
      iState = _KEY;
      iJ = i;
    }
  }
}

line()

int line()

Returns the current 0-based line number.

Implementation
dart
@override
int line() => _line;

newline()

void newline(int i)

Records that a newline was encountered at position i.

Implementation
dart
@override
void newline(int i) {
  _line += 1;
  _pos = i + 1;
}

noSuchMethod() inherited

dynamic noSuchMethod(Invocation invocation)

Invoked when a nonexistent method or property is accessed.

A dynamic member invocation can attempt to call a member which doesn't exist on the receiving object. Example:

dart
dynamic object = 1;
object.add(42); // Statically allowed, run-time error

This invalid code will invoke the noSuchMethod method of the integer 1 with an Invocation representing the .add(42) call and arguments (which then throws).

Classes can override noSuchMethod to provide custom behavior for such invalid dynamic invocations.

A class with a non-default noSuchMethod invocation can also omit implementations for members of its interface. Example:

dart
class MockList<T> implements List<T> {
  noSuchMethod(Invocation invocation) {
    log(invocation);
    super.noSuchMethod(invocation); // Will throw.
  }
}
void main() {
  MockList().add(42);
}

This code has no compile-time warnings or errors even though the MockList class has no concrete implementation of any of the List interface methods. Calls to List methods are forwarded to noSuchMethod, so this code will log an invocation similar to Invocation.method(#add, [42]) and then throw.

If a value is returned from noSuchMethod, it becomes the result of the original invocation. If the value is not of a type that can be returned by the original invocation, a type error occurs at the invocation.

The default behavior is to throw a NoSuchMethodError.

Inherited from Object.

Implementation
dart
@pragma("vm:entry-point")
@pragma("wasm:entry-point")
external dynamic noSuchMethod(Invocation invocation);

parseAt() inherited

Record parseAt(int i)

Inherited from Parser.

Implementation
dart
(Json, int) parseAt(int i) {
  try {
    return _parseTop(i);
    &#47;&#47; ignore: avoid_catching_errors
  } on RangeError {
    throw IncompleteParseException('exhausted input');
  }
}

parseString() inherited

int parseString(int i, FContext ctxt)

Inherited from ByteBasedParser.

Implementation
dart
@override
int parseString(int i, FContext ctxt) {
  final k = parseStringSimple(i + 1, ctxt);

  if (k != -1) {
    ctxt.addStringLimit(atRange(i + 1, k - 1), i, k);
    return k;
  }

  int j = i + 1;
  final sb = StringBuffer();

  int c = byte(j) & 0xff;

  while (c != 34) {
    if (c == 92) {
      final b = byte(j + 1);

      switch (b) {
        case 98:
          sb.write('\b');
          j += 2;
        case 102:
          sb.write('\f');
          j += 2;
        case 110:
          sb.write('\n');
          j += 2;
        case 114:
          sb.write('\r');
          j += 2;
        case 116:
          sb.write('\t');
          j += 2;

        case 34:
          sb.write('"');
          j += 2;
        case 47:
          sb.write('&#47;');
          j += 2;
        case 92:
          sb.write('\\');
          j += 2;

        &#47;&#47; if there's a problem then descape will explode
        case 117:
          final jj = j + 2;
          sb.write(descape(jj, atRange(jj, jj + 4)));
          j += 6;

        default:
          die(
            j,
            'invalid escape sequence (\\${String.fromCharCode(b)})',
            chars: 1,
          );
      }
    } else if (c < 32) {
      die(j, 'control char ($c) in string', chars: 1);
    } else if (c < 128) {
      &#47;&#47; 1-byte UTF-8 sequence
      sb.write(String.fromCharCode(c));
      j += 1;
    } else if ((c & 224) == 192) {
      &#47;&#47; 2-byte UTF-8 sequence
      sb.write(atRange(j, j + 2));
      j += 2;
    } else if ((c & 240) == 224) {
      &#47;&#47; 3-byte UTF-8 sequence
      sb.write(atRange(j, j + 3));
      j += 3;
    } else if ((c & 248) == 240) {
      &#47;&#47; 4-byte UTF-8 sequence
      sb.write(atRange(j, j + 4));
      j += 4;
    } else {
      die(j, 'invalid UTF-8 encoding');
    }

    c = byte(j) & 0xff;
  }

  j += 1;

  ctxt.addStringLimit(sb.toString(), i, j);
  return j;
}

parseStringSimple() inherited

int parseStringSimple(int i, FContext ctxt)

Inherited from ByteBasedParser.

Implementation
dart
int parseStringSimple(int i, FContext ctxt) {
  int j = i;
  int c = byte(j) & 0xff;

  while (c != 34) {
    if (c < 32) die(j, 'control char ($c) in string', chars: 1);
    if (c == 92) return -1;

    j += 1;
    c = byte(j) & 0xff;
  }
  return j + 1;
}

reset()

int reset(int i)

Called at the start of each parse step; may adjust i to account for buffer compaction (relevant for async parsers). Returns the adjusted position.

Implementation
dart
@override
int reset(int i) {
  if (_offset >= 1000000) {
    final diff = _offset;

    _curr -= diff;
    _len -= diff;
    _offset = 0;
    _pos -= diff;

    _data.removeRange(0, diff);

    return i - diff;
  } else {
    return i;
  }
}

toString() inherited

String toString()

A string representation of this object.

Some classes have a default textual representation, often paired with a static parse function (like int.parse). These classes will provide the textual representation as their string representation.

Other classes have no meaningful textual representation that a program will care about. Such classes will typically override toString to provide useful information when inspecting the object, mainly for debugging or logging.

Inherited from Object.

Implementation
dart
external String toString();

Operators

operator ==() inherited

bool operator ==(Object other)

The equality operator.

The default behavior for all Objects is to return true if and only if this object and other are the same object.

Override this method to specify a different equality relation on a class. The overriding method must still be an equivalence relation. That is, it must be:

  • Total: It must return a boolean for all arguments. It should never throw.

  • Reflexive: For all objects o, o == o must be true.

  • Symmetric: For all objects o1 and o2, o1 == o2 and o2 == o1 must either both be true, or both be false.

  • Transitive: For all objects o1, o2, and o3, if o1 == o2 and o2 == o3 are true, then o1 == o3 must be true.

The method should also be consistent over time, so whether two objects are equal should only change if at least one of the objects was modified.

If a subclass overrides the equality operator, it should override the hashCode method as well to maintain consistency.

Inherited from Object.

Implementation
dart
external bool operator ==(Object other);