private JsonToken nextInObject(boolean firstElement) throws IOException {
    /*
     * Read delimiters. Either a comma/semicolon separating this and the
     * previous name-value pair, or a close brace to denote the end of the
     * object.
     */
    if (firstElement) {
      /* Peek to see if this is the empty object. */
      switch (nextNonWhitespace()) {
        case '}':
          pop();
          return token = JsonToken.END_OBJECT;
        default:
          pos--;
      }
    } else {
      switch (nextNonWhitespace()) {
        case '}':
          pop();
          return token = JsonToken.END_OBJECT;
        case ';':
        case ',':
          break;
        default:
          throw syntaxError("Unterminated object");
      }
    }

    /* Read the name. */
    int quote = nextNonWhitespace();
    switch (quote) {
      case '\'':
        checkLenient(); // fall-through
      case '"':
        name = nextString((char) quote);
        break;
      default:
        checkLenient();
        pos--;
        name = nextLiteral(false);
        if (name.isEmpty()) {
          throw syntaxError("Expected name");
        }
    }

    replaceTop(JsonScope.DANGLING_NAME);
    return token = JsonToken.NAME;
  }
  private JsonToken objectValue() throws IOException {
    /*
     * Read the name/value separator. Usually a colon ':'. In lenient mode
     * we also accept an equals sign '=', or an arrow "=>".
     */
    switch (nextNonWhitespace()) {
      case ':':
        break;
      case '=':
        checkLenient();
        if ((pos < limit || fillBuffer(1)) && buffer[pos] == '>') {
          pos++;
        }
        break;
      default:
        throw syntaxError("Expected ':'");
    }

    replaceTop(JsonScope.NONEMPTY_OBJECT);
    return nextValue();
  }
  /** Returns the type of the next token without consuming it. */
  public JsonToken peek() throws IOException {
    if (token != null) {
      return token;
    }

    switch (peekStack()) {
      case EMPTY_DOCUMENT:
        replaceTop(JsonScope.NONEMPTY_DOCUMENT);
        JsonToken firstToken = nextValue();
        if (!lenient && token != JsonToken.BEGIN_ARRAY && token != JsonToken.BEGIN_OBJECT) {
          throw new IOException("Expected JSON document to start with '[' or '{' but was " + token);
        }
        return firstToken;
      case EMPTY_ARRAY:
        return nextInArray(true);
      case NONEMPTY_ARRAY:
        return nextInArray(false);
      case EMPTY_OBJECT:
        return nextInObject(true);
      case DANGLING_NAME:
        return objectValue();
      case NONEMPTY_OBJECT:
        return nextInObject(false);
      case NONEMPTY_DOCUMENT:
        try {
          JsonToken token = nextValue();
          if (lenient) {
            return token;
          }
          throw syntaxError("Expected EOF");
        } catch (EOFException e) {
          return token = JsonToken.END_DOCUMENT; // TODO: avoid throwing here?
        }
      case CLOSED:
        throw new IllegalStateException("JsonReader is closed");
      default:
        throw new AssertionError();
    }
  }
  private JsonToken nextInArray(boolean firstElement) throws IOException {
    if (firstElement) {
      replaceTop(JsonScope.NONEMPTY_ARRAY);
    } else {
      /* Look for a comma before each element after the first element. */
      switch (nextNonWhitespace()) {
        case ']':
          pop();
          return token = JsonToken.END_ARRAY;
        case ';':
          checkLenient(); // fall-through
        case ',':
          break;
        default:
          throw syntaxError("Unterminated array");
      }
    }

    switch (nextNonWhitespace()) {
      case ']':
        if (firstElement) {
          pop();
          return token = JsonToken.END_ARRAY;
        }
        // fall-through to handle ",]"
      case ';':
      case ',':
        /* In lenient mode, a 0-length literal means 'null' */
        checkLenient();
        pos--;
        value = "null";
        return token = JsonToken.NULL;
      default:
        pos--;
        return nextValue();
    }
  }