Parsing json directly using input stream
You're using the content
stream in a wrong way. Look at the marked lines:
InputStream content = entity.getContent();
// (1)
BufferedReader bReader = new BufferedReader(new InputStreamReader(content));
String line;
while ((line = bReader.readLine()) != null) {
builder.append(line);
}
// (2)
JsonReader reader = new JsonReader(new InputStreamReader(content));
reader.setLenient(true);
readGson(reader);
The issue here is that you wrap the same stream twice (on marked line).
- After first wrap, you go through the stream collecting the lines and concatenating them in a
builder
. You must be aware that effectively thebReader
is just a wrapper around thecontent
stream. So while you are collecting the lines, the content from thecontent
stream is eaten. So, the condition(line = ...) != null
isfalse
when thecontent
stream is at the end of input. - Then you wrap the
content
stream again - for parsing JSON contents. But the stream is already at the end of input here, so there is nothing to consume by the JSON reader. And that exactly is the meaning of the line:java.io.EOFException: End of input at line 1 column 1
of your exception.
What you have to do is to read through the content
stream only once. So, you have several options here:
- Don't save received contents to a
String
. You delete the first wrap and the loop which builds the response string using abuilder
. If that's not an option then I recommend: Save the whole response to a
String
. Note: you can use theEntityUtils
class for this since it will do the work for you:HttpEntity entity = response.getEntity();
// All the work is done for you here :)
String jsonContent = EntityUtils.toString(entity);
// Create a Reader from String
Reader stringReader = new StringReader(jsonContent);
// Pass the string reader to JsonReader constructor
JsonReader reader = new JsonReader(stringReader);
reader.setLenient(true);
readGson(reader);
...
// at the end of method return the JSON response
return jsonContent;
How to convert InputStream to JsonArray Object using Java
AFAIU, this is expected, since Your JSON object is only partially valid.
Although it is not a valid JSON array either, it could be parsed into JSONArray
after small modifications (mind the starting and closing brackets and a comma between the objects):
[
{"id":4,"productId":9949940,"data":"product data 1","productPrice":"653.90"},
{"id":5,"productId":4940404,"data":"product data 2","productPrice":"94.12"}
]
Or, alternatively, You could split the input into individual JSON objects by hand and parse them one by one.
Streaming large JSON from input stream efficiently in Java
In short,
- your code does not work because it implements a wrong algorithm;
- JsonPath, as it has been suggested, seems to be a good DSL implementation, but it uses a DOM approach collecting entire JSON tree into memory, therefore you'll run into OOM again.
You have two solutions:
- implement a proper algorithm within your current approach (and I agree you were on a right way);
- try implementing something similar to what JsonPath implements breaking down the problem to smaller ones supporting really streaming approach.
I wouldn't document much of my code since it's pretty easy to understand and adapt to other libraries, but you can develop a more advanced thing of the following code using Java 17 (w/ preview features enabled) and javax.json
(+ some Lombok for Java boilerplate):
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public final class PathJsonParser
implements JsonParser, Iterator<JsonParser.Event> {
private static final int DEFAULT_PATH_LENGTH = 32;
private final JsonParser jsonParser;
private final AbstractPathElement[] path;
private int last;
public static PathJsonParser create(final JsonParser jsonParser) {
final int maxPathLength = DEFAULT_PATH_LENGTH;
final PathJsonParser pathJsonParser = new PathJsonParser(jsonParser, new AbstractPathElement[maxPathLength]);
pathJsonParser.path[0] = AbstractPathElement.Root.instance;
for ( int i = 1; i < maxPathLength; i++ ) {
pathJsonParser.path[i] = new AbstractPathElement.Container();
}
return pathJsonParser;
}
@Override
public Event next() {
final Event event = jsonParser.next();
switch ( event ) {
case START_ARRAY -> {
path[last].tryIncreaseIndex();
path[++last].reset(JsonValue.ValueType.ARRAY);
}
case START_OBJECT -> {
path[last].tryIncreaseIndex();
path[++last].reset(JsonValue.ValueType.OBJECT);
}
case KEY_NAME -> path[last].setKeyName(jsonParser.getString());
case VALUE_STRING -> path[last].tryIncreaseIndex();
case VALUE_NUMBER -> path[last].tryIncreaseIndex();
case VALUE_TRUE -> path[last].tryIncreaseIndex();
case VALUE_FALSE -> path[last].tryIncreaseIndex();
case VALUE_NULL -> path[last].tryIncreaseIndex();
case END_OBJECT -> --last;
case END_ARRAY -> --last;
default -> throw new AssertionError(event);
}
return event;
}
public boolean matchesRoot(final int at) {
@Nullable
final AbstractPathElement e = tryElementAt(at);
return e != null && e.matchesRoot();
}
public boolean matchesIndex(final int at, final IntPredicate predicate) {
@Nullable
final AbstractPathElement e = tryElementAt(at);
return e != null && e.matchesIndex(predicate);
}
public boolean matchesName(final int at, final Predicate<? super String> predicate) {
@Nullable
final AbstractPathElement e = tryElementAt(at);
return e != null && e.matchesName(predicate);
}
// @formatter:off
@Override public boolean hasNext() { return jsonParser.hasNext(); }
@Override public String getString() { return jsonParser.getString(); }
@Override public boolean isIntegralNumber() { return jsonParser.isIntegralNumber(); }
@Override public int getInt() { return jsonParser.getInt(); }
@Override public long getLong() { return jsonParser.getLong(); }
@Override public BigDecimal getBigDecimal() { return jsonParser.getBigDecimal(); }
@Override public JsonLocation getLocation() { return jsonParser.getLocation(); }
@Override @SuppressWarnings("MethodDoesntCallSuperMethod") public JsonObject getObject() { return jsonParser.getObject(); }
@Override @SuppressWarnings("MethodDoesntCallSuperMethod") public JsonValue getValue() { return jsonParser.getValue(); }
@Override @SuppressWarnings("MethodDoesntCallSuperMethod") public JsonArray getArray() { return jsonParser.getArray(); }
@Override @SuppressWarnings("MethodDoesntCallSuperMethod") public Stream<JsonValue> getArrayStream() { return jsonParser.getArrayStream(); }
@Override @SuppressWarnings("MethodDoesntCallSuperMethod") public Stream<Map.Entry<String, JsonValue>> getObjectStream() { return jsonParser.getObjectStream(); }
@Override @SuppressWarnings("MethodDoesntCallSuperMethod") public Stream<JsonValue> getValueStream() { return jsonParser.getValueStream(); }
@Override @SuppressWarnings("MethodDoesntCallSuperMethod") public void skipArray() { jsonParser.skipArray(); }
@Override @SuppressWarnings("MethodDoesntCallSuperMethod") public void skipObject() { jsonParser.skipObject(); }
@Override public void close() { jsonParser.close(); }
// @formatter:on
@Nullable
private AbstractPathElement tryElementAt(final int at) {
final int pathAt;
if ( at >= 0 ) {
pathAt = at;
} else {
pathAt = last + at + 1;
}
if ( pathAt < 0 || pathAt > last ) {
return null;
}
return path[pathAt];
}
private abstract static sealed class AbstractPathElement
permits AbstractPathElement.Root, AbstractPathElement.Container {
abstract void reset(JsonValue.ValueType valueType);
abstract void setKeyName(String keyName);
abstract void tryIncreaseIndex();
abstract boolean matchesRoot();
abstract boolean matchesIndex(IntPredicate predicate);
abstract boolean matchesName(Predicate<? super String> predicate);
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
private static final class Root
extends AbstractPathElement {
private static final AbstractPathElement instance = new Root();
@Override
void reset(final JsonValue.ValueType valueType) {
throw new UnsupportedOperationException();
}
@Override
void setKeyName(final String keyName) {
throw new UnsupportedOperationException();
}
@Override
void tryIncreaseIndex() {
// do nothing
}
@Override
boolean matchesRoot() {
return true;
}
@Override
boolean matchesIndex(final IntPredicate predicate) {
return false;
}
@Override
boolean matchesName(final Predicate<? super String> predicate) {
return false;
}
}
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
private static final class Container
extends AbstractPathElement {
private static final String NO_KEY_NAME = null;
private static final int NO_INDEX = -1;
private JsonValue.ValueType valueType;
private String keyName = NO_KEY_NAME;
private int index = NO_INDEX;
@Override
void reset(final JsonValue.ValueType valueType) {
this.valueType = valueType;
keyName = NO_KEY_NAME;
index = NO_INDEX;
}
@Override
void setKeyName(final String keyName) {
this.keyName = keyName;
}
@Override
void tryIncreaseIndex() {
if ( valueType == JsonValue.ValueType.ARRAY ) {
index++;
}
}
@Override
boolean matchesRoot() {
return false;
}
@Override
boolean matchesIndex(final IntPredicate predicate) {
return switch ( valueType ) {
case ARRAY -> index != NO_INDEX && predicate.test(index);
case OBJECT -> false;
case STRING, NUMBER, TRUE, FALSE, NULL -> throw new AssertionError(valueType);
};
}
@Override
boolean matchesName(final Predicate<? super String> predicate) {
return switch ( valueType ) {
case ARRAY -> false;
case OBJECT -> !Objects.equals(keyName, NO_KEY_NAME) && predicate.test(keyName);
case STRING, NUMBER, TRUE, FALSE, NULL -> throw new AssertionError(valueType);
};
}
}
}
}
Example of use:
public final class PathJsonParserTest {
// $.files.0.content.0.fileContent.subList.0.subList.0.text
private static boolean matches(final PathJsonParser parser) {
return parser.matchesName(-1, name -> name.equals("text"))
&& parser.matchesIndex(-2, index -> true)
&& parser.matchesName(-3, name -> name.equals("subList"))
&& parser.matchesIndex(-4, index -> true)
&& parser.matchesName(-5, name -> name.equals("subList"))
&& parser.matchesName(-6, name -> name.equals("fileContent"))
&& parser.matchesIndex(-7, index -> true)
&& parser.matchesName(-8, name -> name.equals("content"))
&& parser.matchesIndex(-9, index -> true)
&& parser.matchesName(-10, name -> name.equals("files"))
&& parser.matchesRoot(-11);
}
@Test
public void test()
throws IOException {
try ( final PathJsonParser parser = PathJsonParser.create(JsonParsers.openFromResource(PathJsonParserTest.class, "input.json")) ) {
for ( ; parser.hasNext(); parser.next() ) {
if ( matches(parser) ) {
parser.next();
System.out.println(parser.getValue());
}
}
}
}
}
Of course, not that cool-looking as JsonPath is, but you can do the following:
- implement a matcher builder API to make it look nicer;
- implement a JSON Path-compliant parser to build matchers;
- wrap the
for/if/next()
pattern into a generic algorithm (similar to whatBufferedReader.readLine()
implements or wrap it for Stream API); - implement some kind of simple JSON-to-objects deserializer.
Or, if possible, find a good code generator that can generate a streamed parser having as small runtime cost as possible (its outcome would be very similar to yours, but working). (Ping me please if you are aware of any.)
Convert InputStream into JSON
Make use of Jackson JSON parser.
Refer - Jackson Home
The only thing you need to do -
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> jsonMap = mapper.readValue(inputStream, Map.class);
Now jsonMap
will contain the JSON.
Java read JSON input stream
Wrap it with a BufferedReader
and start reading the data from it:
StringBuilder sb = new StringBuilder();
try (BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
String line;
while ( (line = br.readLine()) != null) {
sb.append(line).append(System.lineSeparator());
}
String content = sb.toString();
//as example, you can see the content in console output
System.out.println(content);
}
Once you have it as a String, parse it with a library like Gson or Jackson.
Related Topics
How to Find Out My MySQL Url, Host, Port and Username
How to Pass Authentication Credentials in Vba
How to Unpackage and Repackage a War File
How to Test If Json Collection Object Is Empty in Java
How to Test Class Which Implements Runnable With Junit
Jackson Deserialization Issue for Zoneddatetime
Codility Tape Equilibrium Getting Zero on Some Cases
Array Pairwise Matching in Java Give Error Also Store Data Between Two Similar Element
Spring JPA Repository: Prevent Update on Save
Json to Pojo With Nested Values
How to Exclude an Android App from Battery Optimization Using Code
Filling a List With All Enum Values in Java
This Program Is About String Compression in Java
How to Specify the Path for Getresourceasstream() Method in Java
How to Shut Down a Spring Boot Command-Line Application
Statefulbeantocsv With Column Headers