package org.embulk.spi.json; import java.util.List; import java.util.ArrayList; import java.util.Map; import java.util.HashMap; import java.io.InputStream; import java.io.Closeable; import java.io.IOException; import org.msgpack.value.Value; import org.msgpack.value.ValueFactory; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser.Feature; import com.fasterxml.jackson.core.JsonToken; public class JsonParser { public interface Stream extends Closeable { Value next() throws IOException; void close() throws IOException; } private final JsonFactory factory; public JsonParser() { this.factory = new JsonFactory(); factory.enable(Feature.ALLOW_UNQUOTED_CONTROL_CHARS); factory.enable(Feature.ALLOW_NON_NUMERIC_NUMBERS); } public Stream open(InputStream in) throws IOException { return new StreamParseContext(factory, in); } public Value parse(String json) { return new SingleParseContext(factory, json).parse(); } private static String sampleJsonString(String json) { if (json.length() < 100) { return json; } else { return json.substring(0, 97) + "..."; } } private static class StreamParseContext extends AbstractParseContext implements Stream { public StreamParseContext(JsonFactory factory, InputStream in) throws IOException, JsonParseException { super(createParser(factory, in)); } private static com.fasterxml.jackson.core.JsonParser createParser(JsonFactory factory, InputStream in) throws IOException { try { return factory.createParser(in); } catch (IOException ex) { throw ex; } catch (Exception ex) { throw new JsonParseException("Failed to parse JSON", ex); } } @Override public void close() throws IOException { parser.close(); } @Override protected String sampleJsonString() { return "in"; } } private static class SingleParseContext extends AbstractParseContext { private final String json; public SingleParseContext(JsonFactory factory, String json) { super(createParser(factory, json)); this.json = json; } private static com.fasterxml.jackson.core.JsonParser createParser(JsonFactory factory, String json) { try { return factory.createParser(json); } catch (Exception ex) { throw new JsonParseException("Failed to parse JSON: "+JsonParser.sampleJsonString(json), ex); } } public Value parse() { try { Value v = next(); if (v == null) { throw new JsonParseException("Unable to parse empty string"); } return v; } catch (IOException ex) { throw new JsonParseException("Failed to parse JSON: "+sampleJsonString(), ex); } } @Override protected String sampleJsonString() { return JsonParser.sampleJsonString(json); } } private static abstract class AbstractParseContext { protected final com.fasterxml.jackson.core.JsonParser parser; public AbstractParseContext(com.fasterxml.jackson.core.JsonParser parser) { this.parser = parser; } protected abstract String sampleJsonString(); public Value next() throws IOException { try { JsonToken token = parser.nextToken(); if (token == null) { return null; } return jsonTokenToValue(token); } catch (com.fasterxml.jackson.core.JsonParseException ex) { throw new JsonParseException("Failed to parse JSON: "+sampleJsonString(), ex); } catch (IOException ex) { throw ex; } catch (JsonParseException ex) { throw ex; } catch (RuntimeException ex) { throw new JsonParseException("Failed to parse JSON: "+sampleJsonString(), ex); } } private Value jsonTokenToValue(JsonToken token) throws IOException { switch(token) { case VALUE_NULL: return ValueFactory.newNil(); case VALUE_TRUE: return ValueFactory.newBoolean(true); case VALUE_FALSE: return ValueFactory.newBoolean(false); case VALUE_NUMBER_FLOAT: return ValueFactory.newFloat(parser.getDoubleValue()); case VALUE_NUMBER_INT: try { return ValueFactory.newInteger(parser.getLongValue()); } catch (com.fasterxml.jackson.core.JsonParseException ex) { return ValueFactory.newInteger(parser.getBigIntegerValue()); } case VALUE_STRING: return ValueFactory.newString(parser.getText()); case START_ARRAY: { List<Value> list = new ArrayList<>(); while (true) { token = parser.nextToken(); if (token == JsonToken.END_ARRAY) { return ValueFactory.newArray(list); } else if (token == null) { throw new JsonParseException("Unexpected end of JSON at "+parser.getTokenLocation() + " while expecting an element of an array: " + sampleJsonString()); } list.add(jsonTokenToValue(token)); } } case START_OBJECT: Map<Value, Value> map = new HashMap<>(); while (true) { token = parser.nextToken(); if (token == JsonToken.END_OBJECT) { return ValueFactory.newMap(map); } else if (token == null) { throw new JsonParseException("Unexpected end of JSON at "+parser.getTokenLocation() + " while expecting a key of object: " + sampleJsonString()); } String key = parser.getCurrentName(); if (key == null) { throw new JsonParseException("Unexpected token "+token+" at "+parser.getTokenLocation() + ": " + sampleJsonString()); } token = parser.nextToken(); if (token == null) { throw new JsonParseException("Unexpected end of JSON at "+parser.getTokenLocation() + " while expecting a value of object: " + sampleJsonString()); } Value value = jsonTokenToValue(token); map.put(ValueFactory.newString(key), value); } case VALUE_EMBEDDED_OBJECT: case FIELD_NAME: case END_ARRAY: case END_OBJECT: case NOT_AVAILABLE: default: throw new JsonParseException("Unexpected token "+token+" at "+parser.getTokenLocation() + ": " + sampleJsonString()); } } } }