AsyncParser final
final class AsyncParser extends ParserA 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:
- AsyncParserMode.singleValue — one complete JSON value
- AsyncParserMode.valueStream — multiple whitespace-separated values
- AsyncParserMode.unwrapArray — elements of a top-level JSON array
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
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 hashCodeThe 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
external int get hashCode;runtimeType no setter inherited
Type get runtimeTypeA representation of the runtime type of the object.
Inherited from Object.
Implementation
external Type get runtimeType;Methods
absorb()
Feeds buf into the parser and returns any Json values completed so far. Does not signal end-of-input; call finish when done.
Implementation
Either<ParseException, IList<Json>> absorb(Uint8List buf) {
_done = false;
_data.addAll(buf);
_len = _data.length;
return _churn();
}absorbString()
Encodes buf as UTF-8 and feeds it to absorb.
Implementation
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
@override
String at(int i) => String.fromCharCode(byte(i));atCodeUnit()
int atCodeUnit(int i)Returns the UTF-16 code unit at position i.
Implementation
@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
@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
@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
@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
@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
@override
void close() {}column()
int column(int i)Returns the 0-based column of position i.
Implementation
@override
int column(int i) => i - _pos;descape() inherited
String descape(int pos, String s)Inherited from Parser.
Implementation
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
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()
Feeds buf and signals end-of-input, returning all remaining Json values or a ParseException if the input is incomplete or invalid.
Implementation
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()
Encodes buf as UTF-8 and calls finalAbsorb.
Implementation
Either<ParseException, IList<Json>> finalAbsorbString(String buf) =>
finalAbsorb(utf8.encoder.convert(buf));finish()
Signals end-of-input and flushes any remaining buffered data, returning completed Json values or a ParseException.
Implementation
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
(Json, int) iparse(
int state,
int j,
FContext context,
List<FContext> stack,
) {
// 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) {
// '\n'
newline(i);
iJ = i + 1;
} else if (c == 32 || c == 9 || c == 13) {
// ' ', '\t', '\r'
iJ = i + 1;
} else if (iState == _DATA) {
// we are inside an object or array expecting to see data
if (c == 91) {
// '['
iState = _ARRBEG;
iJ = i + 1;
iStack.add(iContext);
iContext = FContext.array(i);
} else if (c == 123) {
// '{'
iState = _OBJBEG;
iJ = i + 1;
iStack.add(iContext);
iContext = FContext.object(i);
} else if ((c >= 48 && c <= 57) || c == 45) {
// '0'-'9' or '-'
iJ = _parseNum(i, iContext);
iState = iContext.isObject ? _OBJEND : _ARREND;
} else if (c == 34) {
// '"'
iJ = parseString(i, iContext);
iState = iContext.isObject ? _OBJEND : _ARREND;
} else if (c == 116) {
// 't'
iContext.addValueAt(_parseTrue(i), i);
iState = iContext.isObject ? _OBJEND : _ARREND;
iJ = i + 4;
} else if (c == 102) {
// 'f'
iContext.addValueAt(_parseFalse(i), i);
iState = iContext.isObject ? _OBJEND : _ARREND;
iJ = i + 5;
} else if (c == 110) {
// '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)) || // ']'
(c == 125 && (iState == _OBJEND || iState == _OBJBEG))) {
// '}'
// we are inside an array or object and have seen a key or a closing
// 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) {
// we are in an object expecting to see a key.
if (c == 34) {
// '"'
iJ = parseString(i, iContext);
iState = _SEP;
} else {
die(i, 'expected "');
}
} else if (iState == _SEP) {
// we are in an object just after a key, expecting to see a colon.
if (c == 58) {
// ':'
iState = _DATA;
iJ = i + 1;
} else {
die(i, 'expected :');
}
} else if (iState == _ARREND) {
// we are in an array, expecting to see a comma (before more data).
if (c == 44) {
// ','
iState = _DATA;
iJ = i + 1;
} else {
die(i, 'expected ] or ,');
}
} else if (iState == _OBJEND) {
// we are in an object, expecting to see a comma (before more data).
if (c == 44) {
// ','
iState = _KEY;
iJ = i + 1;
} else {
die(i, 'expected } or ,');
}
} else if (iState == _ARRBEG) {
// we are starting an array, expecting to see data or a closing bracket.
iState = _DATA;
iJ = i;
} else {
// 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
@override
int line() => _line;newline()
void newline(int i)Records that a newline was encountered at position i.
Implementation
@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:
dynamic object = 1;
object.add(42); // Statically allowed, run-time errorThis 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:
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
@pragma("vm:entry-point")
@pragma("wasm:entry-point")
external dynamic noSuchMethod(Invocation invocation);parseAt() inherited
Record parseAt(int i)Inherited from Parser.
Implementation
(Json, int) parseAt(int i) {
try {
return _parseTop(i);
// ignore: avoid_catching_errors
} on RangeError {
throw IncompleteParseException('exhausted input');
}
}parseString() inherited
int parseString(int i, FContext ctxt)Inherited from ByteBasedParser.
Implementation
@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('/');
j += 2;
case 92:
sb.write('\\');
j += 2;
// 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) {
// 1-byte UTF-8 sequence
sb.write(String.fromCharCode(c));
j += 1;
} else if ((c & 224) == 192) {
// 2-byte UTF-8 sequence
sb.write(atRange(j, j + 2));
j += 2;
} else if ((c & 240) == 224) {
// 3-byte UTF-8 sequence
sb.write(atRange(j, j + 3));
j += 3;
} else if ((c & 248) == 240) {
// 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
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
@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
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 == omust be true.Symmetric: For all objects
o1ando2,o1 == o2ando2 == o1must either both be true, or both be false.Transitive: For all objects
o1,o2, ando3, ifo1 == o2ando2 == o3are true, theno1 == o3must 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
external bool operator ==(Object other);