package com.gvaneyck.rtmp.encoding; import java.util.ArrayList; import java.util.LinkedList; /** * Simple JSON parser. Throws a RuntimeException on parsing error * * @author Gabriel Van Eyck */ public class JSON { /** * Parses JSON from a string * * @param json The JSON to parse * @return The parsed object */ public static Object parse(String json) { if (json == null) return null; LinkedList<Character> buff = new LinkedList<Character>(); for (int i = 0; i < json.length(); i++) buff.add(json.charAt(i)); return parse(buff); } /** * Parses JSON from a linked list * * @param json The JSON to parse * @return The parsed object */ private static Object parse(LinkedList<Character> json) { Character c = removeNonSpace(json); switch (c) { case '{': return parseObject(json); case '[': return parseArray(json); case '"': return parseString(json); default: json.addFirst(c); return parseOther(json); } } /** * Parses a JSON object * Assumes the opening curly brace has been consumed * * @param json The JSON to parse * @return The parsed object as a map */ private static ObjectMap parseObject(LinkedList<Character> json) { ObjectMap ret = new ObjectMap(); // Check for empty objects Character c = removeNonSpace(json); if (c == '}') return ret; json.addFirst(c); do { c = removeNonSpace(json); if (c != '"') throw new JSONParsingException("JSON object member name did not start with a double quote: '" + c + "'"); String key = parseString(json); c = removeNonSpace(json); if (c != ':') throw new JSONParsingException("JSON object missing colon for value or member: '" + c + "'"); Object val = parse(json); ret.put(key, val); } while ((c = removeNonSpace(json)) == ','); if (c != '}') throw new JSONParsingException("JSON object did not end with a curly brace: '" + c + "'"); return ret; } /** * Parses a JSON array * Assumes the opening square bracket has been consumed * * @param json The JSON to parse * @return The parsed array */ private static Object[] parseArray(LinkedList<Character> json) { // Check for empty array Character c = removeNonSpace(json); if (c == ']') return new Object[0]; json.addFirst(c); ArrayList<Object> temp = new ArrayList<Object>(); do { temp.add(parse(json)); c = removeNonSpace(json); } while (c == ','); if (c != ']') throw new JSONParsingException("JSON array did not end with a square bracket: '" + c + "'"); Object[] ret = new Object[temp.size()]; for (int i = 0; i < temp.size(); i++) ret[i] = temp.get(i); return ret; } /** * Parses a JSON string * Assumes the opening double quote has been consumed * * @param json The JSON to parse * @return The parsed string */ private static String parseString(LinkedList<Character> json) { boolean postBackslash = false; StringBuilder buff = new StringBuilder(); Character c; while ((c = json.removeFirst()) != '"' || postBackslash) { if (!postBackslash) { if (c == '\\') postBackslash = true; else buff.append(c); } else { switch (c) { case '"': buff.append('"'); break; case '\\': buff.append('\\'); break; case '/': buff.append('/'); break; case 'b': buff.append('\b'); break; case 'f': buff.append('\f'); break; case 'n': buff.append('\n'); break; case 'r': buff.append('\r'); break; case 't': buff.append('\t'); break; case 'u': StringBuilder temp = new StringBuilder(); for (int i = 0; i < 4; i++) temp.append(json.removeFirst()); buff.append((char)Integer.parseInt(temp.toString(), 16)); break; } postBackslash = false; } } return buff.toString(); } /** * Parses JSON numbers, booleans, and null * * @param json The JSON to parse * @return The parsed object */ private static Object parseOther(LinkedList<Character> json) { Character c = removeNonSpace(json); switch (c) { case 't': for (int i = 0; i < 3; i++) json.removeFirst(); return true; case 'f': for (int i = 0; i < 4; i++) json.removeFirst(); return false; case 'n': for (int i = 0; i < 3; i++) json.removeFirst(); return null; default: StringBuilder buff = new StringBuilder(); boolean isInt = true; while (c == '-' || c >= '0' && c <= '9' || c == '.') { if (c == '.') isInt = false; buff.append(c); c = json.removeFirst(); } json.addFirst(c); if (buff.length() == 0) throw new JSONParsingException("Tried parsing number but got: '" + c + "'"); if (isInt) return Integer.parseInt(buff.toString()); else return Double.parseDouble(buff.toString()); } } /** * Extracts the first non-whitespace character from the JSON string * * @param json The JSON string * @return The first non-whitespace character */ private static Character removeNonSpace(LinkedList<Character> json) { Character ret; while (Character.isWhitespace(ret = json.removeFirst())) {} return ret; } }