
com.bazaarvoice.emodb.common.json.JsonStreamingArrayParser Maven / Gradle / Ivy
package com.bazaarvoice.emodb.common.json;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.google.common.base.Throwables;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.PeekingIterator;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import static com.google.common.base.Preconditions.checkState;
/**
* Incrementally parses an JSON array of objects, returning the results as an iterator. Allows parsing an
* arbitrary amount of data, potentially more than could fit in memory at one time.
*/
public class JsonStreamingArrayParser extends AbstractIterator implements PeekingIterator, Closeable {
private final JsonParser _jp;
private final ObjectReader _reader;
private final Class extends T> _type;
public JsonStreamingArrayParser(InputStream in, Class extends T> elementType) {
this(in, CustomJsonObjectMapperFactory.build(), elementType);
}
public JsonStreamingArrayParser(InputStream in, ObjectMapper mapper, Class extends T> elementType) {
this(in, mapper, (Type) elementType);
}
public JsonStreamingArrayParser(InputStream in, TypeReference extends T> elementType) {
this(in, CustomJsonObjectMapperFactory.build(), elementType.getType());
}
public JsonStreamingArrayParser(InputStream in, ObjectMapper mapper, TypeReference extends T> elementType) {
this(in, mapper, elementType.getType());
}
private JsonStreamingArrayParser(InputStream in, ObjectMapper mapper, Type elementType) {
try {
JavaType javaType = mapper.constructType(elementType);
//noinspection unchecked
_type = (Class extends T>) javaType.getRawClass();
_jp = mapper.getFactory().createParser(in);
_reader = mapper.readerFor(javaType);
// Parse at least the first byte of the response to make sure the input stream is valid.
if (_jp.nextToken() != JsonToken.START_ARRAY) {
throw new JsonParseException("Invalid JSON response, expected content to start with '[': " +
_jp.getCurrentToken(), _jp.getTokenLocation());
}
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
@Override
protected T computeNext() {
try {
if (_jp.nextToken() == JsonToken.END_ARRAY) {
checkState(_jp.nextToken() == null, "Expected EOF in JavaScript input stream.");
_jp.close();
return endOfData();
}
return _type.cast(_reader.readValue(_jp));
} catch (IOException e) {
// If the root cause is a JsonProcessingException then throw it as a JsonStreamProcessingException.
if (isJsonProcessingException(e)) {
throw new JsonStreamProcessingException(e);
}
// We already parsed the first few bytes in the constructor and verified that the InputStream looked valid
// so if there's an unexpected end of input here it likely means we lost the connection to the server.
// In practice this a JsonStreamingEOFException or a TruncatedChunkException or something similar.
throw new JsonStreamingEOFException(e);
}
}
/**
* Returns true if the exception is a JsonProcessingException whose root cause is not a premature end of data,
* since this is more likely caused by a connection error.
*/
private boolean isJsonProcessingException(Exception e) {
// Unfortunately Jackson doesn't have specific subclasses for each parse exception, so we have to use the
// more brittle approach of checking the exception message.
return e instanceof JsonProcessingException &&
!(e instanceof JsonParseException &&
e.getMessage() != null &&
e.getMessage().startsWith("Unexpected end-of-input"));
}
@Override
public void close() throws IOException {
_jp.close();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy