Skip to content

Commit 20c7202

Browse files
authored
Merge pull request #40 from avaje/feature/markIncomplete
Fix to JsonWriter to not flush underlying outputStream when error during serialisation
2 parents b181d5a + dca3e8f commit 20c7202

File tree

10 files changed

+107
-3
lines changed

10 files changed

+107
-3
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.example.customer;
2+
3+
import io.avaje.jsonb.Json;
4+
5+
import java.util.UUID;
6+
7+
@Json
8+
public record IErrOnRead(UUID id, String firstName, String lastName) {
9+
10+
public String lastName() {
11+
throw new IllegalArgumentException("error reading lastName");
12+
}
13+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package org.example.customer;
2+
3+
import io.avaje.jsonb.JsonException;
4+
import io.avaje.jsonb.JsonType;
5+
import io.avaje.jsonb.Jsonb;
6+
import org.junit.jupiter.api.Test;
7+
8+
import java.io.ByteArrayOutputStream;
9+
import java.util.UUID;
10+
11+
import static org.assertj.core.api.Assertions.assertThat;
12+
13+
class IErrOnReadTest {
14+
15+
@Test
16+
void toJson_withError() {
17+
IErrOnRead bean = new IErrOnRead(UUID.randomUUID(), "first", "last");
18+
Jsonb jsonb = Jsonb.builder().build();
19+
JsonType<IErrOnRead> type = jsonb.type(IErrOnRead.class);
20+
21+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
22+
try {
23+
type.toJson(bean, baos);
24+
} catch (JsonException expectedForTest) {
25+
assertThat(expectedForTest.getCause()).hasMessage("error reading lastName");
26+
}
27+
assertThat(baos.toByteArray()).describedAs("no flush to outputStream expected").hasSize(0);
28+
}
29+
}

jsonb-jackson/src/main/java/io/avaje/jsonb/jackson/JacksonWriter.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ final class JacksonWriter implements JsonWriter {
2929
this.serializeEmpty = serializeEmpty;
3030
}
3131

32+
@Override
33+
public void markIncomplete() {
34+
// on close don't flush or close the underlying OutputStream
35+
generator.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
36+
generator.disable(JsonGenerator.Feature.FLUSH_PASSED_TO_STREAM);
37+
generator.disable(JsonGenerator.Feature.AUTO_CLOSE_JSON_CONTENT);
38+
}
39+
3240
@Override
3341
public void close() {
3442
try {

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,4 +228,11 @@ public interface JsonWriter extends Closeable, Flushable {
228228
*/
229229
void close();
230230

231+
/**
232+
* Mark the generated json as not completed due to an error.
233+
* <p>
234+
* This typically means not to flush or close an underlying OutputStream which
235+
* allows it to be reset to then write some error response content instead.
236+
*/
237+
void markIncomplete();
231238
}

jsonb/src/main/java/io/avaje/jsonb/core/DJsonType.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,20 +75,25 @@ public final byte[] toJsonBytes(T value) {
7575

7676
@Override
7777
public final void toJson(T value, JsonWriter writer) {
78-
adapter.toJson(writer, value);
78+
try {
79+
adapter.toJson(writer, value);
80+
} catch (RuntimeException e) {
81+
writer.markIncomplete();
82+
throw new JsonException(e);
83+
}
7984
}
8085

8186
@Override
8287
public final void toJson(T value, Writer writer) {
8388
try (JsonWriter jsonWriter = jsonb.writer(writer)) {
84-
adapter.toJson(jsonWriter, value);
89+
toJson(value, jsonWriter);
8590
}
8691
}
8792

8893
@Override
8994
public final void toJson(T value, OutputStream outputStream) {
9095
try (JsonWriter writer = jsonb.writer(outputStream)) {
91-
adapter.toJson(writer, value);
96+
toJson(value, writer);
9297
}
9398
close(outputStream);
9499
}

jsonb/src/main/java/io/avaje/jsonb/spi/DelegateJsonWriter.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,4 +186,8 @@ public void close() {
186186
delegate.close();
187187
}
188188

189+
@Override
190+
public void markIncomplete() {
191+
delegate.markIncomplete();
192+
}
189193
}

jsonb/src/main/java/io/avaje/jsonb/stream/JGenerator.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ final class JGenerator implements JsonGenerator {
6363
private final ArrayStack<JsonNames> nameStack = new ArrayStack<>();
6464
private JsonNames currentNames;
6565
private boolean allNames;
66+
private boolean incomplete;
6667

6768
JGenerator() {
6869
this(512);
@@ -83,6 +84,7 @@ JsonGenerator prepare(OutputStream targetStream) {
8384
pretty = false;
8485
nameStack.clear();
8586
allNames = false;
87+
incomplete = false;
8688
return this;
8789
}
8890

@@ -381,6 +383,11 @@ public byte[] toByteArray() {
381383
return Arrays.copyOf(buffer, position);
382384
}
383385

386+
@Override
387+
public void markIncomplete() {
388+
incomplete = true;
389+
}
390+
384391
@Override
385392
public void flush() {
386393
if (target != null && position != 0) {
@@ -395,6 +402,7 @@ public void flush() {
395402

396403
@Override
397404
public void close() {
405+
if (incomplete) return;
398406
flush();
399407
if (target != null) {
400408
try {

jsonb/src/main/java/io/avaje/jsonb/stream/JsonGenerator.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,4 +131,8 @@ interface JsonGenerator extends Closeable, Flushable {
131131
*/
132132
byte[] toByteArray();
133133

134+
/**
135+
* Mark that json generation was not completed due to an error.
136+
*/
137+
void markIncomplete();
134138
}

jsonb/src/main/java/io/avaje/jsonb/stream/JsonWriteAdapter.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ public <T> T unwrap(Class<T> underlying) {
3131
return (T)generator;
3232
}
3333

34+
@Override
35+
public void markIncomplete() {
36+
generator.markIncomplete();
37+
}
38+
3439
@Override
3540
public void close() {
3641
generator.close();

jsonb/src/test/java/io/avaje/jsonb/stream/DieselAdapterTest.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,27 @@ void write_to_OutputStream() {
8181
assertThat(os1.toString()).isEqualTo("{\"one\":\"hi\"}");
8282
}
8383

84+
@Test
85+
void write_markIncomplete_withError() {
86+
ByteArrayOutputStream os = new ByteArrayOutputStream();
87+
try {
88+
try (JsonWriter jw = adapter.writer(os)) {
89+
jw.beginObject();
90+
jw.name("one");
91+
jw.value("I will be incomplete");
92+
jw.markIncomplete();
93+
throwAnError();
94+
}
95+
} catch (Exception e) {
96+
// ignore
97+
}
98+
assertThat(os.toString()).isEqualTo("");
99+
}
100+
101+
private void throwAnError() {
102+
throw new IllegalStateException("foo");
103+
}
104+
84105
private void writeHello(JsonWriter jw, String message) {
85106
jw.beginObject();
86107
jw.name("one");

0 commit comments

Comments
 (0)