addCharacter method

  1. @override
void addCharacter(
  1. String character
)
override

Implementation

@override
void addCharacter(String character) {
  // Debug logging disabled to reduce noise
  // print(
  //   'MAP[$propertyPath] State: $_state | Char: |$character| | KeyBuffer: |$_keyBuffer| | ChildDone: ${_activeChildDelegate?.isDone}',
  // );

  if (_state == MapParserState.readingKey) {
    if (character == '"') {
      _state = MapParserState.waitingForValue;
      return;
    } else {
      _keyBuffer += character;
      return;
    }
  }

  if (_state == MapParserState.readingValue) {
    // Store the delegate reference before calling addCharacter
    // because onComplete callback might clear it
    final childDelegate = _activeChildDelegate;
    childDelegate?.addCharacter(character);
    final childIsDone = childDelegate?.isDone ?? false;
    if (childIsDone) {
      _state = MapParserState.waitingForCommaOrEnd;
      _activeChildDelegate = null;
      // Only reprocess if the child is NOT a list or map
      // (lists and maps consume their own closing brackets)
      final childType = childDelegate.runtimeType.toString();
      if (childType == 'ListPropertyDelegate' ||
          childType == 'MapPropertyDelegate') {
        return; // Don't reprocess - child consumed the closing bracket
      }
      // For other types (numbers, strings, etc), reprocess the delimiter
    } else {
      return;
    }
  }

  if (_state == MapParserState.waitingForValue) {
    if (character == " " || character == ":") return;
    // Add this key to our list of keys
    _keys.add(_keyBuffer);

    // FIRST: Determine the type and create the PropertyStream
    // This ensures the controller exists before the delegate tries to use it
    final childPath = newPath(_keyBuffer);
    final Type streamType;
    if (character == '"') {
      streamType = String;
    } else if (character == '{') {
      streamType = Map;
    } else if (character == '[') {
      streamType = List;
    } else if (character == 't' || character == 'f') {
      streamType = bool;
    } else if (character == 'n') {
      streamType = Null;
    } else {
      streamType = num;
    }
    // Create the property stream (which creates the controller)
    parserController.getPropertyStream(childPath, streamType);

    // THEN: Create child delegate with a closure that checks if it's still active
    PropertyDelegate? childDelegate;
    childDelegate = createDelegate(
      character,
      propertyPath: childPath,
      jsonStreamParserController: parserController,
      onComplete: () {
        // Only notify parent if this child is still the active one
        if (_activeChildDelegate == childDelegate) {
          onChildComplete();
        }
      },
    );
    _activeChildDelegate = childDelegate;
    _activeChildDelegate!.addCharacter(character);
    _state = MapParserState.readingValue;
    return;
  }

  if (_firstCharacter && character == '{') {
    _firstCharacter = false;
    return;
  }

  if (_state == MapParserState.waitingForCommaOrEnd) {
    // Skip whitespace
    if (character == ' ' ||
        character == '\t' ||
        character == '\n' ||
        character == '\r') {
      return;
    }
    if (character == ',') {
      _state = MapParserState.waitingForKey;
      _keyBuffer = "";
      return;
    } else if (character == '}') {
      _completeMap();
      return;
    }
  }

  if (_state == MapParserState.waitingForKey) {
    // Skip whitespace
    if (character == ' ' ||
        character == '\t' ||
        character == '\n' ||
        character == '\r') {
      return;
    }
    if (character == '"') {
      _state = MapParserState.readingKey;
      return;
    }
    if (character == "}") {
      _completeMap();
      return;
    }
  }

  return;
}