// Copyright 2013 Michel Kraemer // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package de.undercouch.citeproc.helper.json; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import de.undercouch.citeproc.helper.json.JsonLexer.Type; /** * Parses JSON tokens to maps * @author Michel Kraemer */ public class JsonParser { private final JsonLexer lexer; /** * Constructs a new JSON parser * @param lexer a JSON lexer to read from */ public JsonParser(JsonLexer lexer) { this.lexer = lexer; } /** * Parses an object into a map * @return the parsed object * @throws IOException if the input stream could not be read or if * the input stream contained an unexpected token */ public Map<String, Object> parseObject() throws IOException { Type t = lexer.readNextToken(); if (t != Type.START_OBJECT) { throw new IOException("Unexpected token: " + t); } return parseObjectInternal(); } /** * Parses an object into a map without reading the * {@link Type#START_OBJECT} token * @return the parsed object * @throws IOException if the input stream could not be read or if * the input stream contained an unexpected token */ private Map<String, Object> parseObjectInternal() throws IOException { Map<String, Object> result = new HashMap<>(); Type t; while (true) { t = lexer.readNextToken(); if (t == Type.END_OBJECT) { break; } if (!result.isEmpty()) { //skip comma and read next token if (t != Type.COMMA) { throw new IOException("Unexpected token: " + t); } t = lexer.readNextToken(); } //first token must be the name if (t != Type.STRING) { throw new IOException("Unexpected token: " + t); } String name = lexer.readString(); //skip colon t = lexer.readNextToken(); if (t != Type.COLON) { throw new IOException("Unexpected token: " + t); } //next token must be the value t = lexer.readNextToken(); Object value = readValue(t); result.put(name, value); } return result; } /** * Parses an array * @return the parsed array * @throws IOException if the input stream could not be read or if * the input stream contained an unexpected token */ public List<Object> parseArray() throws IOException { Type t = lexer.readNextToken(); if (t != Type.START_ARRAY) { throw new IOException("Unexpected token: " + t); } return parseArrayInternal(); } /** * Parses an array without reading the {@link Type#START_ARRAY} token * @return the parsed array * @throws IOException if the input stream could not be read or if * the input stream contained an unexpected token */ private List<Object> parseArrayInternal() throws IOException { List<Object> result = new ArrayList<>(); Type t; while (true) { t = lexer.readNextToken(); if (t == Type.END_ARRAY) { break; } if (!result.isEmpty()) { //skip comma and read next token if (t != Type.COMMA) { throw new IOException("Unexpected token: " + t); } t = lexer.readNextToken(); } //read value Object value = readValue(t); result.add(value); } return result; } /** * Reads a value for a given type * @param t the type * @return the value * @throws IOException if the input stream could not be read or if * the input stream contained an unexpected token */ private Object readValue(Type t) throws IOException { switch (t) { case START_OBJECT: return parseObjectInternal(); case START_ARRAY: return parseArrayInternal(); case STRING: return lexer.readString(); case NUMBER: return lexer.readNumber(); case TRUE: return true; case FALSE: return false; case NULL: return null; default: throw new IOException("Unexpected token: " + t); } } }