/** * Parses the current JSON type from a {@link String}. */ public T from(String s) throws JsonParserException { return new JsonParser(s).parse(clazz); } }
/** * Expects a given string at the current position. */ private void consumeKeyword(char first, char[] expected) throws JsonParserException { for (int i = 0; i < expected.length; i++) if (advanceChar() != expected[i]) throw createHelpfulException(first, expected, i); // The token should end with something other than an ASCII letter if (isAsciiLetter(peekChar())) throw createHelpfulException(first, expected, expected.length); }
/** * Parse a single JSON value from the string, expecting an EOF at the end. */ @SuppressWarnings("unchecked") <T> T parse(Class<T> clazz) throws JsonParserException { advanceToken(); Object parsed = currentValue(); if (advanceToken() != Token.EOF) throw createParseException(null, "Expected end of input, got " + token, true); if (clazz != Object.class && (parsed == null || clazz != parsed.getClass())) throw createParseException(null, "JSON did not contain the correct type, expected " + clazz.getName() + ".", true); return (T)(parsed); }
/** * Advances a character, throwing if it is illegal in the context of a JSON string. */ private char stringChar() throws JsonParserException { int c = advanceChar(); if (c == -1) throw createParseException(null, "String was not terminated before end of input", true); if (c < 32) throw createParseException(null, "Strings may not contain control characters: 0x" + Integer.toString(c, 16), false); return (char)c; }
int c = advanceChar(); while (isWhitespace(c)) c = advanceChar(); case '[': // Inlined function to avoid additional stack JsonArray list = new JsonArray(); if (advanceToken() != Token.ARRAY_END) while (true) { list.add(currentValue()); if (advanceToken() == Token.ARRAY_END) break; if (token != Token.COMMA) throw createParseException(null, "Expected a comma or end of the array instead of " + token, true); if (advanceToken() == Token.ARRAY_END) throw createParseException(null, "Trailing comma found in array", true); case '{': // Inlined function to avoid additional stack JsonObject map = new JsonObject(); if (advanceToken() != Token.OBJECT_END) while (true) { if (token != Token.STRING) throw createParseException(null, "Expected STRING, got " + token, true); String key = (String)value; if (advanceToken() != Token.COLON) throw createParseException(null, "Expected COLON, got " + token, true); advanceToken(); map.put(key, currentValue()); if (advanceToken() == Token.OBJECT_END)
/** * Throws a helpful exception based on the current alphanumeric token. */ private JsonParserException createHelpfulException(char first, char[] expected, int failurePosition) throws JsonParserException { // Build the first part of the token StringBuilder errorToken = new StringBuilder(first + (expected == null ? "" : new String(expected, 0, failurePosition))); // Consume the whole pseudo-token to make a better error message while (isAsciiLetter(peekChar()) && errorToken.length() < 15) errorToken.append((char)advanceChar()); return createParseException(null, "Unexpected token '" + errorToken + "'" + (expected == null ? "" : ". Did you mean '" + first + new String(expected) + "'?"), true); }
reusableBuffer.setLength(0); while (true) { char c = stringChar(); return reusableBuffer.toString(); case '\\': int escape = advanceChar(); switch (escape) { case -1: throw createParseException(null, "EOF encountered in the middle of a string escape", false); case 'b': reusableBuffer.append('\b'); break; case 'u': reusableBuffer.append((char)(stringHexChar() << 12 | stringHexChar() << 8 // | stringHexChar() << 4 | stringHexChar())); break; default: throw createParseException(null, "Invalid escape: \\" + (char)escape, false);
while (isDigitCharacter(peekChar())) { char next = (char)advanceChar(); isDouble = next == '.' || next == 'e' || next == 'E' || isDouble; end++; if (number.charAt(1) == '.') { if (number.length() == 2) throw createParseException(null, "Malformed number: " + number, true); } else if (number.charAt(1) != 'e' && number.charAt(1) != 'E') throw createParseException(null, "Malformed number: " + number, true); if (number.charAt(2) == '.') { if (number.length() == 3) throw createParseException(null, "Malformed number: " + number, true); } else if (number.charAt(2) != 'e' && number.charAt(2) != 'E') throw createParseException(null, "Malformed number: " + number, true); } else if (number.charAt(1) == '.') { throw createParseException(null, "Malformed number: " + number, true); if (number.length() == 1) return 0; throw createParseException(null, "Malformed number: " + number, true); throw createParseException(null, "Malformed number: " + number, true); return new BigInteger(number); } catch (NumberFormatException e) { throw createParseException(e, "Malformed number: " + number, true);
/** * Starts parsing a JSON value at the current token position. */ private Object currentValue() throws JsonParserException { // Only a value start token should appear when we're in the context of parsing a JSON value if (token.isValue) return value; throw createParseException(null, "Expected JSON value, got " + token, true); }
@Override public Array parseArray(String json) throws JsonParserException { return JsonParser.array().from(json); }
int c = advanceChar(); while (isWhitespace(c)) c = advanceChar(); case '[': // Inlined function to avoid additional stack JsonArray list = new JsonArray(); if (advanceToken() != Token.ARRAY_END) while (true) { list.add(currentValue()); if (advanceToken() == Token.ARRAY_END) break; if (token != Token.COMMA) throw createParseException(null, "Expected a comma or end of the array instead of " + token, true); if (advanceToken() == Token.ARRAY_END) throw createParseException(null, "Trailing comma found in array", true); case '{': // Inlined function to avoid additional stack JsonObject map = new JsonObject(); if (advanceToken() != Token.OBJECT_END) while (true) { if (token != Token.STRING) throw createParseException(null, "Expected STRING, got " + token, true); String key = (String)value; if (advanceToken() != Token.COLON) throw createParseException(null, "Expected COLON, got " + token, true); advanceToken(); map.put(key, currentValue()); if (advanceToken() == Token.OBJECT_END)
/** * Throws a helpful exception based on the current alphanumeric token. */ private JsonParserException createHelpfulException(char first, char[] expected, int failurePosition) throws JsonParserException { // Build the first part of the token StringBuilder errorToken = new StringBuilder(first + (expected == null ? "" : new String(expected, 0, failurePosition))); // Consume the whole pseudo-token to make a better error message while (isAsciiLetter(peekChar()) && errorToken.length() < 15) errorToken.append((char)advanceChar()); return createParseException(null, "Unexpected token '" + errorToken + "'" + (expected == null ? "" : ". Did you mean '" + first + new String(expected) + "'?"), true); }
reusableBuffer.setLength(0); while (true) { char c = stringChar(); return reusableBuffer.toString(); case '\\': int escape = advanceChar(); switch (escape) { case -1: throw createParseException(null, "EOF encountered in the middle of a string escape", false); case 'b': reusableBuffer.append('\b'); break; case 'u': reusableBuffer.append((char)(stringHexChar() << 12 | stringHexChar() << 8 // | stringHexChar() << 4 | stringHexChar())); break; default: throw createParseException(null, "Invalid escape: \\" + (char)escape, false);
while (isDigitCharacter(peekChar())) { char next = (char)advanceChar(); isDouble = next == '.' || next == 'e' || next == 'E' || isDouble; end++; if (number.charAt(1) == '.') { if (number.length() == 2) throw createParseException(null, "Malformed number: " + number, true); } else if (number.charAt(1) != 'e' && number.charAt(1) != 'E') throw createParseException(null, "Malformed number: " + number, true); if (number.charAt(2) == '.') { if (number.length() == 3) throw createParseException(null, "Malformed number: " + number, true); } else if (number.charAt(2) != 'e' && number.charAt(2) != 'E') throw createParseException(null, "Malformed number: " + number, true); } else if (number.charAt(1) == '.') { throw createParseException(null, "Malformed number: " + number, true); if (number.length() == 1) return 0; throw createParseException(null, "Malformed number: " + number, true); throw createParseException(null, "Malformed number: " + number, true); return new BigInteger(number); } catch (NumberFormatException e) { throw createParseException(e, "Malformed number: " + number, true);
/** * Advances a character, throwing if it is illegal in the context of a JSON string. */ private char stringChar() throws JsonParserException { int c = advanceChar(); if (c == -1) throw createParseException(null, "String was not terminated before end of input", true); if (c < 32) throw createParseException(null, "Strings may not contain control characters: 0x" + Integer.toString(c, 16), false); return (char)c; }
/** * Starts parsing a JSON value at the current token position. */ private Object currentValue() throws JsonParserException { // Only a value start token should appear when we're in the context of parsing a JSON value if (token.isValue) return value; throw createParseException(null, "Expected JSON value, got " + token, true); }
@Override public Array parseArray(String json) throws JsonParserException { return JsonParser.array().from(json); }
int c = advanceChar(); while (isWhitespace(c)) c = advanceChar(); case '[': // Inlined function to avoid additional stack JsonArray list = new JsonArray(); if (advanceToken() != Token.ARRAY_END) while (true) { list.add(currentValue()); if (advanceToken() == Token.ARRAY_END) break; if (token != Token.COMMA) throw createParseException(null, "Expected a comma or end of the array instead of " + token, true); if (advanceToken() == Token.ARRAY_END) throw createParseException(null, "Trailing comma found in array", true); case '{': // Inlined function to avoid additional stack JsonObject map = new JsonObject(); if (advanceToken() != Token.OBJECT_END) while (true) { if (token != Token.STRING) throw createParseException(null, "Expected STRING, got " + token, true); String key = (String)value; if (advanceToken() != Token.COLON) throw createParseException(null, "Expected COLON, got " + token, true); advanceToken(); map.put(key, currentValue()); if (advanceToken() == Token.OBJECT_END)
/** * Expects a given string at the current position. */ private void consumeKeyword(char first, char[] expected) throws JsonParserException { for (int i = 0; i < expected.length; i++) if (advanceChar() != expected[i]) throw createHelpfulException(first, expected, i); // The token should end with something other than an ASCII letter if (isAsciiLetter(peekChar())) throw createHelpfulException(first, expected, expected.length); }
/** * Throws a helpful exception based on the current alphanumeric token. */ private JsonParserException createHelpfulException(char first, char[] expected, int failurePosition) throws JsonParserException { // Build the first part of the token StringBuilder errorToken = new StringBuilder(first + (expected == null ? "" : new String(expected, 0, failurePosition))); // Consume the whole pseudo-token to make a better error message while (isAsciiLetter(peekChar()) && errorToken.length() < 15) errorToken.append((char)advanceChar()); return createParseException(null, "Unexpected token '" + errorToken + "'" + (expected == null ? "" : ". Did you mean '" + first + new String(expected) + "'?"), true); }