Skip to content

Commit 82e7706

Browse files
committed
#21 - Add JsonWriter unwrap() ... for example, gives access to the underlying Jackson JsonGenerator
1 parent 86417ad commit 82e7706

File tree

9 files changed

+124
-0
lines changed

9 files changed

+124
-0
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ public Builder failOnUnknown(boolean failOnUnknown) {
6464
* Build and return the JacksonAdapter.
6565
*/
6666
public JacksonAdapter build() {
67+
if (jsonFactory == null) {
68+
jsonFactory = new JsonFactory();
69+
}
6770
return new JacksonAdapter(serializeNulls, serializeEmpty, failOnUnknown, jsonFactory);
6871
}
6972
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ public void pretty(boolean pretty) {
5656
}
5757
}
5858

59+
@SuppressWarnings("unchecked")
60+
@Override
61+
public <T> T unwrap(Class<T> underlying) {
62+
return (T) generator;
63+
}
64+
5965
@Override
6066
public void serializeNulls(boolean serializeNulls) {
6167
this.serializeNulls = serializeNulls;
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package io.avaje.jsonb.jackson;
2+
3+
import com.fasterxml.jackson.core.JsonGenerator;
4+
import com.fasterxml.jackson.core.JsonpCharacterEscapes;
5+
import com.fasterxml.jackson.core.SerializableString;
6+
import com.fasterxml.jackson.core.io.CharacterEscapes;
7+
import com.fasterxml.jackson.core.io.SerializedString;
8+
import io.avaje.jsonb.JsonWriter;
9+
import io.avaje.jsonb.Jsonb;
10+
import org.example.Address;
11+
import org.example.MyComponent;
12+
import org.junit.jupiter.api.Test;
13+
14+
import java.io.StringWriter;
15+
16+
import static org.assertj.core.api.Assertions.assertThat;
17+
18+
class UnwrapJacksonGeneratorTest {
19+
20+
@Test
21+
void writer_unwrap() {
22+
Jsonb jsonb = Jsonb.newBuilder()
23+
.adapter(JacksonAdapter.newBuilder().build())
24+
.add(new MyComponent())
25+
.build();
26+
27+
StringWriter sw = new StringWriter();
28+
try (JsonWriter writer = jsonb.writer(sw)) {
29+
writer.unwrap(JsonGenerator.class).setCharacterEscapes(new HTMLCharacterEscapes());
30+
31+
Address withHtml = new Address().street("<p>my-html-content-with[&][\"][']</p>");
32+
jsonb.toJson(withHtml, writer);
33+
}
34+
assertThat(sw.toString()).isEqualTo("{\"street\":\"&#60;p&#62;my-html-content-with[&#38;][&#34;][&#39;]&#60;/p&#62;\"}");
35+
}
36+
37+
static class HTMLCharacterEscapes extends JsonpCharacterEscapes {
38+
39+
@Override
40+
public int[] getEscapeCodesForAscii() {
41+
int[] asciiEscapes = CharacterEscapes.standardAsciiEscapesForJSON();
42+
// and force escaping of a few others:
43+
asciiEscapes['<'] = CharacterEscapes.ESCAPE_CUSTOM;
44+
asciiEscapes['>'] = CharacterEscapes.ESCAPE_CUSTOM;
45+
asciiEscapes['&'] = CharacterEscapes.ESCAPE_CUSTOM;
46+
asciiEscapes['"'] = CharacterEscapes.ESCAPE_CUSTOM;
47+
asciiEscapes['\''] = CharacterEscapes.ESCAPE_CUSTOM;
48+
return asciiEscapes;
49+
}
50+
51+
@Override
52+
public SerializableString getEscapeSequence(int ch) {
53+
switch (ch) {
54+
case '&' : return new SerializedString("&#38;");
55+
case '<' : return new SerializedString("&#60;");
56+
case '>' : return new SerializedString("&#62;");
57+
case '\"' : return new SerializedString("&#34;");
58+
case '\'' : return new SerializedString("&#39;");
59+
default : return super.getEscapeSequence(ch);
60+
}
61+
}
62+
}
63+
}

jsonb-jakarta/src/main/java/io/avaje/jsonb/jakarta/JakartaJsonWriter.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ final class JakartaJsonWriter implements JsonWriter {
2525
this.serializeEmpty = serializeEmpty;
2626
}
2727

28+
@Override
29+
public <T> T unwrap(Class<T> underlying) {
30+
throw new RuntimeException("Not implemented");
31+
}
32+
2833
@Override
2934
public void serializeNulls(boolean serializeNulls) {
3035
this.serializeNulls = serializeNulls;

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,30 @@
2727
*/
2828
public interface JsonWriter extends Closeable, Flushable {
2929

30+
/**
31+
* Unwrap the underlying generator being used.
32+
* <p>
33+
* We do this to get access to the underlying generator. For the case when
34+
* using jackson-core we get access to Jackson JsonGenerator and can then
35+
* do Jackson specific things like set custom escaping.
36+
*
37+
* <pre>{@code
38+
*
39+
* try (JsonWriter writer = jsonb.writer(new StringWriter())) {
40+
*
41+
* // get access to the underlying Jackson JsonGenerator
42+
* var generator = writer.unwrap(JsonGenerator.class)
43+
*
44+
* // do Jackson specific things like ...
45+
* generator.setCharacterEscapes(new HTMLCharacterEscapes());
46+
*
47+
* jsonb.toJson(myBean, writer);
48+
* }
49+
*
50+
* }</pre>
51+
*/
52+
<T> T unwrap(Class<T> underlying);
53+
3054
/**
3155
* Set to serialise null values or not.
3256
*/

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,13 @@ static Builder newBuilder() {
127127
*/
128128
void toJson(Object any, OutputStream outputStream);
129129

130+
/**
131+
* Write to the given writer.
132+
* <p>
133+
* This is a convenience method for {@code jsonb.type(Object.class).toJson(any, writer) }
134+
*/
135+
void toJson(Object any, JsonWriter jsonWriter);
136+
130137
/**
131138
* Return the JsonType used to read and write json for the given class.
132139
*

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ public void toJson(Object any, OutputStream outputStream) {
6666
anyType.toJson(any, outputStream);
6767
}
6868

69+
@Override
70+
public void toJson(Object any, JsonWriter jsonWriter) {
71+
anyType.toJson(any, jsonWriter);
72+
}
73+
6974
@Override
7075
public PropertyNames properties(String... names) {
7176
return io.properties(names);

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ public DelegateJsonWriter(JsonWriter delegate) {
1616
this.delegate = delegate;
1717
}
1818

19+
@Override
20+
public <T> T unwrap(Class<T> underlying) {
21+
return delegate.unwrap(underlying);
22+
}
23+
1924
@Override
2025
public final void serializeNulls(boolean serializeNulls) {
2126
delegate.serializeNulls(serializeNulls);

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ final class JsonWriteAdapter implements JsonWriter {
2525
this.serializeEmpty = serializeEmpty;
2626
}
2727

28+
@SuppressWarnings("unchecked")
29+
@Override
30+
public <T> T unwrap(Class<T> underlying) {
31+
return (T)generator;
32+
}
33+
2834
@Override
2935
public void close() {
3036
generator.close();

0 commit comments

Comments
 (0)