Skip to content

Commit 0e1d00a

Browse files
committed
#23 - Stream support, processing as Stream<MyBean> etc
1 parent ac2e19a commit 0e1d00a

File tree

17 files changed

+478
-28
lines changed

17 files changed

+478
-28
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package org.example.customer.stream;
2+
3+
import io.avaje.jsonb.Json;
4+
5+
@Json
6+
public record MyBasic(int id, String name) {
7+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package org.example.customer.stream;
2+
3+
import io.avaje.jsonb.Json;
4+
5+
import java.util.List;
6+
7+
@Json
8+
public record MyNested(int nest, String desc, List<MyBasic> innerStream) {
9+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package org.example.customer.stream;
2+
3+
import io.avaje.jsonb.JsonReader;
4+
import io.avaje.jsonb.JsonType;
5+
import io.avaje.jsonb.Jsonb;
6+
import org.junit.jupiter.api.Test;
7+
8+
import java.util.List;
9+
import java.util.stream.Stream;
10+
11+
import static org.assertj.core.api.Assertions.assertThat;
12+
13+
class StreamBasicTest {
14+
15+
Jsonb jsonb = Jsonb.newBuilder().build();
16+
JsonType<MyBasic> type = jsonb.type(MyBasic.class);
17+
List<MyBasic> basics = List.of(new MyBasic(1, "a"), new MyBasic(2, "b"), new MyBasic(3, "c"));
18+
19+
@Test
20+
void stream_toJson() {
21+
String asJson = type.stream().toJson(basics.stream());
22+
assertThat(asJson).isEqualTo("[{\"id\":1,\"name\":\"a\"},{\"id\":2,\"name\":\"b\"},{\"id\":3,\"name\":\"c\"}]");
23+
}
24+
25+
@Test
26+
void stream_traditionalArray() {
27+
String arrayJson = jsonb.toJson(basics);
28+
StringBuilder sb = new StringBuilder();
29+
30+
try (JsonReader reader = jsonb.reader(arrayJson)) {
31+
try (Stream<MyBasic> asStream = type.stream(reader)) {
32+
asStream.forEach(sb::append);
33+
}
34+
}
35+
36+
assertThat(sb.toString()).isEqualTo("MyBasic[id=1, name=a]MyBasic[id=2, name=b]MyBasic[id=3, name=c]");
37+
38+
// same test but using - JsonType<Stream<T>>
39+
JsonType<Stream<MyBasic>> streamJsonType = type.stream();
40+
41+
sb = new StringBuilder();
42+
streamJsonType
43+
.fromJson(arrayJson)
44+
.forEach(sb::append);
45+
46+
assertThat(sb.toString()).isEqualTo("MyBasic[id=1, name=a]MyBasic[id=2, name=b]MyBasic[id=3, name=c]");
47+
}
48+
49+
@Test
50+
void stream_newLineDelimited() {
51+
String arrayJson = jsonb.toJson(basics);
52+
String newLineDelimitedJson = arrayJson.replaceAll("[\\[|\\]]", "").replace("},{", "}\n{");
53+
54+
StringBuilder sb = new StringBuilder();
55+
try (JsonReader reader = jsonb.reader(newLineDelimitedJson)) {
56+
Stream<MyBasic> asStream = type.stream(reader);
57+
asStream.forEach(sb::append);
58+
}
59+
60+
assertThat(sb.toString()).isEqualTo("MyBasic[id=1, name=a]MyBasic[id=2, name=b]MyBasic[id=3, name=c]");
61+
62+
// same test but using - JsonType<Stream<T>>
63+
JsonType<Stream<MyBasic>> streamJsonType = type.stream();
64+
65+
sb = new StringBuilder();
66+
try (Stream<MyBasic> myBasicStream = streamJsonType.fromJson(newLineDelimitedJson)) {
67+
myBasicStream.forEach(sb::append);
68+
assertThat(sb.toString()).isEqualTo("MyBasic[id=1, name=a]MyBasic[id=2, name=b]MyBasic[id=3, name=c]");
69+
}
70+
}
71+
72+
@Test
73+
void stream_spaceDelimited() {
74+
String arrayJson = "\n" + jsonb.toJson(basics) + "\n";
75+
String spaceDelimitedJson = arrayJson.replaceAll("[\\[|\\]]", "").replace("},{", "} {");
76+
77+
StringBuilder sb = new StringBuilder();
78+
try (JsonReader reader = jsonb.reader(spaceDelimitedJson)) {
79+
type.stream(reader).forEach(sb::append);
80+
}
81+
82+
assertThat(sb.toString()).isEqualTo("MyBasic[id=1, name=a]MyBasic[id=2, name=b]MyBasic[id=3, name=c]");
83+
}
84+
85+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package org.example.customer.stream;
2+
3+
import io.avaje.jsonb.JsonReader;
4+
import io.avaje.jsonb.JsonType;
5+
import io.avaje.jsonb.Jsonb;
6+
import org.junit.jupiter.api.Test;
7+
8+
import java.util.stream.Stream;
9+
10+
class StreamNestedTest {
11+
12+
@Test
13+
void nested() {
14+
15+
String jsonContent = """
16+
[
17+
{"nest":1, "desc":"d1", "innerStream": [{"id":1,"name":"a"},{"id":2,"name":"b"}] },
18+
{"nest":2, "desc":"d2", "innerStream": [{"id":3,"name":"c"}] },
19+
{"nest":3, "desc":"d3", "innerStream": [{"id":4,"name":"d"},{"id":5,"name":"e"}] }
20+
]
21+
""";
22+
23+
Jsonb jsonb = Jsonb.newBuilder().build();
24+
JsonType<Stream<MyNested>> stream = jsonb.type(MyNested.class).stream();
25+
26+
try (JsonReader reader = jsonb.reader(jsonContent)) {
27+
28+
//reader.
29+
30+
Stream<MyNested> myNestedStream = stream.fromJson(reader);
31+
myNestedStream.forEach( myNested -> {
32+
int nest = myNested.nest();
33+
System.out.println(nest + " " + myNested);
34+
});
35+
}
36+
37+
}
38+
}

jsonb-generator/src/main/java/io/avaje/jsonb/generator/GenericType.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ private String asTypeContainer() {
176176
return "Types.listOf(" + Util.shortName(param.topType()) + ".class)";
177177
case "java.util.Set":
178178
return "Types.setOf(" + Util.shortName(param.topType()) + ".class)";
179+
case "java.util.stream.Stream":
180+
return "Types.streamOf(" + Util.shortName(param.topType()) + ".class)";
179181
}
180182
return "FIXME: Unhandled Container Type " + containerType;
181183
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package io.avaje.jsonb;
2+
3+
import java.io.IOException;
4+
5+
/**
6+
* Thrown when we hit EOF unexpectedly.
7+
*/
8+
public class JsonEofException extends JsonIoException {
9+
10+
public JsonEofException(IOException cause) {
11+
super(cause);
12+
}
13+
14+
public JsonEofException(String message, IOException cause) {
15+
super(message, cause);
16+
}
17+
18+
public JsonEofException(String message) {
19+
super(message);
20+
}
21+
}

jsonb/src/main/java/io/avaje/jsonb/JsonReader.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,20 @@ public interface JsonReader extends Closeable {
3333
*/
3434
void names(PropertyNames names);
3535

36+
/**
37+
* Read the beginning of an ARRAY or x-json-stream.
38+
*/
39+
default void beginStream() {
40+
beginArray();
41+
}
42+
43+
/**
44+
* Read the end of an ARRAY or x-json-stream.
45+
*/
46+
default void endStream() {
47+
endArray();
48+
}
49+
3650
/**
3751
* Read array begin.
3852
*/

jsonb/src/main/java/io/avaje/jsonb/JsonType.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.util.List;
66
import java.util.Map;
77
import java.util.Set;
8+
import java.util.stream.Stream;
89

910
/**
1011
* Provides API to serialise a type to and from JSON.
@@ -67,6 +68,25 @@ public interface JsonType<T> extends JsonView<T> {
6768
*/
6869
JsonType<List<T>> list();
6970

71+
/**
72+
* Return the stream type for this JsonType.
73+
* <p>
74+
* When using this Stream type use a try-with-resources block with the Stream
75+
* to ensure that any underlying resources are closed.
76+
*
77+
* <pre>{@code
78+
*
79+
* JsonType<Stream<MyBean>> type = jsonb.type(MyBean.class).stream();
80+
*
81+
* try (Stream<MyBean> asStream = type.fromJson(content)) {
82+
* // use the stream
83+
* ...
84+
* }
85+
*
86+
* }</pre>
87+
*/
88+
JsonType<Stream<T>> stream();
89+
7090
/**
7191
* Return the set type for this JsonType.
7292
*/
@@ -110,4 +130,25 @@ public interface JsonType<T> extends JsonView<T> {
110130
* @return The value converted from 'object form'.
111131
*/
112132
T fromObject(Object value);
133+
134+
/**
135+
* Return as a Stream that will read the content as the stream is processed.
136+
* <p>
137+
* Use a try-with-resources block with the JsonReader to ensure the underlying
138+
* resources are closed.
139+
*
140+
* <pre>{@code
141+
*
142+
* JsonType<MyBean> type = jsonb.type(MyBean.class);
143+
*
144+
* try (JsonReader reader = jsonb.reader(content)) {
145+
146+
* Stream<MyBean> asStream = type.stream(reader);
147+
* ...
148+
* }
149+
*
150+
* }</pre>
151+
*/
152+
Stream<T> stream(JsonReader reader);
153+
113154
}

jsonb/src/main/java/io/avaje/jsonb/Types.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.List;
2323
import java.util.Map;
2424
import java.util.Set;
25+
import java.util.stream.Stream;
2526

2627
/**
2728
* Factory methods for types.
@@ -53,6 +54,13 @@ public static Type setOf(Type elementType) {
5354
return newParameterizedType(Set.class, elementType);
5455
}
5556

57+
/**
58+
* Returns a Type that is a Stream of the given element type.
59+
*/
60+
public static Type streamOf(Type elementType) {
61+
return newParameterizedType(Stream.class, elementType);
62+
}
63+
5664
/**
5765
* Return the Type for a Map with String keys and the given value element type.
5866
*
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package io.avaje.jsonb.core;
2+
3+
import io.avaje.jsonb.JsonReader;
4+
5+
/**
6+
* Adapter that supports closing the JsonReader when returned object (aka Stream) is closed.
7+
*/
8+
interface DJsonClosable<T> {
9+
10+
/**
11+
* Read from the reader additionally closing the reader when done.
12+
* <p>
13+
* Used when returning a Stream that should close the JsonReader when the
14+
* Stream is closed.
15+
*/
16+
T fromJsonWithClose(JsonReader reader);
17+
}

0 commit comments

Comments
 (0)