docs: private Map parseObject(JsonLexer lexer) {
          Map content = new HashMap();
  
          JsonToken previousToken = null;
          JsonToken currentToken = null;
  
          for(;;) {
              currentToken = lexer.nextToken();
  
              if (currentToken == null) {
                  throw new JsonException(
                          "Expected a String key on line: " + lexer.getReader().getLine() + ", " +
                                  "column: " + lexer.getReader().getColumn() + ".\n" +
                                  "But got an unterminated object."
                  );
              }
  
              // expect a string key, or already a closing curly brace
  
              if (currentToken.getType() == CLOSE_CURLY) {
                  return content;
              } else if (currentToken.getType() != STRING) {
                  throw new JsonException(
                          "Expected " + STRING.getLabel() + " key " +
                                  "on line: " + currentToken.getStartLine() + ", " +
                                  "column: " + currentToken.getStartColumn() + ".\n" +
                                  "But got '" + currentToken.getText() + "' instead."
                  );
              }
  
              String mapKey = (String) currentToken.getValue();
  
              currentToken = lexer.nextToken();
  
              if (currentToken == null) {
                  throw new JsonException(
                          "Expected a " + COLON.getLabel() + " " +
                                  "on line: " + lexer.getReader().getLine() + ", " +
                                  "column: " + lexer.getReader().getColumn() + ".\n" +
                                  "But got an unterminated object."
                  );
              }
  
              // expect a colon between the key and value pair
  
              if (currentToken.getType() != COLON) {
                  throw new JsonException(
                          "Expected " + COLON.getLabel() + " " +
                                  "on line: " + currentToken.getStartLine() + ", " +
                                  "column: " + currentToken.getStartColumn() + ".\n" +
                                  "But got '" + currentToken.getText() + "' instead."
                  );
              }
  
              currentToken = lexer.nextToken();
  
              if (currentToken == null) {
                  throw new JsonException(
                          "Expected a value " +
                                  "on line: " + lexer.getReader().getLine() + ", " +
                                  "column: " + lexer.getReader().getColumn() + ".\n" +
                                  "But got an unterminated object."
                  );
              }
  
              // value can be an object, an array, a number, string, boolean or null values
  
              if (currentToken.getType() == OPEN_CURLY) {
                  content.put(mapKey, parseObject(lexer));
              } else if (currentToken.getType() == OPEN_BRACKET) {
                  content.put(mapKey, parseArray(lexer));
              } else if (currentToken.getType().ordinal() >= NULL.ordinal()) {
                  content.put(mapKey, currentToken.getValue());
              } else {
                  throw new JsonException(
                          "Expected a value, an array, or an object " +
                                  "on line: " + currentToken.getStartLine() + ", " +
                                  "column: " + currentToken.getStartColumn() + ".\n" +
                                  "But got '" + currentToken.getText() + "' instead."
                  );
              }
  
              previousToken = currentToken;
              currentToken = lexer.nextToken();
  
              // premature end of the object
  
              if (currentToken == null) {
                  throw new JsonException(
                          "Expected " + CLOSE_CURLY.getLabel() + " or " + COMMA.getLabel() + " " +
                                  "on line: " + previousToken.getEndLine() + ", " +
                                  "column: " + previousToken.getEndColumn() + ".\n" +
                                  "But got an unterminated object."
                  );
              }
  
              // Expect a comma for an upcoming key/value pair
              // or a closing curly brace for the end of the object
              if (currentToken.getType() == CLOSE_CURLY) {
                  break;
              } else if (currentToken.getType() != COMMA) {
                  throw new JsonException(
                          "Expected a value or " + CLOSE_CURLY.getLabel() + " " +
                                  "on line: " + currentToken.getStartLine() + ", " +
                                  "column: " + currentToken.getStartColumn() + ".\n" +
                                  "But got '" + currentToken.getText() + "' instead."
                  );
              }
          }
  
          return content;
      }