Skip to content

Commit e172deb

Browse files
authored
Merge pull request #19 from avaje/feature/JsonRaw
Add @Json.Raw support (similar to Jackson @JsonRawValue)
2 parents 8f8d659 + a9fcb8f commit e172deb

File tree

24 files changed

+470
-11
lines changed

24 files changed

+470
-11
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package org.example.customer.raw;
2+
3+
import io.avaje.jsonb.Json;
4+
5+
@Json
6+
public class WithRawContent {
7+
8+
final long id;
9+
10+
final String name;
11+
12+
@Json.Raw
13+
String content;
14+
15+
public WithRawContent(long id, String name) {
16+
this.id = id;
17+
this.name = name;
18+
}
19+
20+
public long id() {
21+
return id;
22+
}
23+
24+
public String name() {
25+
return name;
26+
}
27+
28+
public String content() {
29+
return content;
30+
}
31+
32+
public WithRawContent content(String content) {
33+
this.content = content;
34+
return this;
35+
}
36+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package org.example.customer.raw;
2+
3+
import io.avaje.jsonb.JsonType;
4+
import io.avaje.jsonb.Jsonb;
5+
import org.junit.jupiter.api.Test;
6+
7+
import java.io.StringReader;
8+
9+
import static org.assertj.core.api.Assertions.assertThat;
10+
11+
class WithRawContentTest {
12+
13+
Jsonb jsonb = Jsonb.newBuilder().build();
14+
JsonType<WithRawContent> withRawType = jsonb.type(WithRawContent.class);
15+
16+
@Test
17+
void toJson() {
18+
String rawJsonContent = "{\"foo\": 99, \"bar\": \"aaa\", \"bazz\":[1,2,3]}";
19+
20+
WithRawContent withRaw = new WithRawContent(42, "rob");
21+
withRaw.content(rawJsonContent);
22+
23+
String asJson = jsonb.toJson(withRaw);
24+
assertThat(asJson).isEqualTo("{\"id\":42,\"name\":\"rob\",\"content\":{\"foo\": 99, \"bar\": \"aaa\", \"bazz\":[1,2,3]}}");
25+
}
26+
27+
@Test
28+
void toJson_when_null() {
29+
WithRawContent withRaw = new WithRawContent(42, "rob");
30+
String asJson = jsonb.toJson(withRaw);
31+
32+
assertThat(asJson).isEqualTo("{\"id\":42,\"name\":\"rob\"}");
33+
}
34+
35+
@Test
36+
void toJson_array() {
37+
WithRawContent withRaw = new WithRawContent(42, "rob");
38+
withRaw.content(" [ 1 , 2 , 3 ] ");
39+
40+
String asJson = jsonb.toJson(withRaw);
41+
assertThat(asJson).isEqualTo("{\"id\":42,\"name\":\"rob\",\"content\": [ 1 , 2 , 3 ] }");
42+
}
43+
44+
@Test
45+
void toFromJson_when_null() {
46+
WithRawContent withRaw = new WithRawContent(42, "rob");
47+
48+
String asJson = jsonb.toJson(withRaw);
49+
assertThat(asJson).isEqualTo("{\"id\":42,\"name\":\"rob\"}");
50+
51+
WithRawContent bean = withRawType.fromJson(asJson);
52+
String content = bean.content();
53+
assertThat(content).isNull();
54+
}
55+
56+
@Test
57+
void toFromJson_when_literalNull() {
58+
59+
WithRawContent withRaw = new WithRawContent(42, "rob");
60+
withRaw.content("null");
61+
62+
String asJson = jsonb.toJson(withRaw);
63+
assertThat(asJson).isEqualTo("{\"id\":42,\"name\":\"rob\"}");
64+
65+
WithRawContent bean = withRawType.fromJson(asJson);
66+
String content = bean.content();
67+
assertThat(content).isNull();
68+
}
69+
70+
@Test
71+
void toFromJson() {
72+
toFromJsonWith("{\"foo\":99}");
73+
toFromJsonWith("[1,2,3]");
74+
toFromJsonWith("42");
75+
toFromJsonWith("true");
76+
toFromJsonWith("false");
77+
toFromJsonWith("{\"foo\": 99, \"bar\": \"aaa\", \"bazz\":[1,2,3]}");
78+
}
79+
80+
@Test
81+
void toFromJson_with_stream() {
82+
toFromJsonWith("{\"foo\":99}", true);
83+
toFromJsonWith("[1,2,3]", true);
84+
toFromJsonWith("42", true);
85+
toFromJsonWith("true", true);
86+
toFromJsonWith("false", true);
87+
toFromJsonWith("{\"foo\": 99, \"bar\": \"aaa\", \"bazz\":[1,2,3]}", true);
88+
}
89+
90+
private void toFromJsonWith(String rawJsonContent) {
91+
toFromJsonWith(rawJsonContent, false);
92+
}
93+
94+
private void toFromJsonWith(String rawJsonContent, boolean stream) {
95+
WithRawContent withRaw = new WithRawContent(42, "rob");
96+
withRaw.content(rawJsonContent);
97+
98+
String asJson = jsonb.toJson(withRaw);
99+
assertThat(asJson).isEqualTo("{\"id\":42,\"name\":\"rob\",\"content\":" + rawJsonContent + "}");
100+
101+
WithRawContent bean;
102+
if (stream) {
103+
bean = withRawType.fromJson(new StringReader(asJson));
104+
} else {
105+
bean = withRawType.fromJson(asJson);
106+
}
107+
String content = bean.content();
108+
assertThat(content).isEqualTo(rawJsonContent);
109+
}
110+
}

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class BeanReader {
2121
private final TypeReader typeReader;
2222
private final String typeProperty;
2323
private FieldReader unmappedField;
24+
private boolean hasRaw;
2425

2526
BeanReader(TypeElement beanType, ProcessingContext context) {
2627
this.beanType = beanType;
@@ -61,6 +62,9 @@ boolean hasSubtypes() {
6162
void read() {
6263
for (FieldReader field : allFields) {
6364
field.addImports(importTypes);
65+
if (field.isRaw()) {
66+
hasRaw = true;
67+
}
6468
if (field.isUnmapped()) {
6569
unmappedField = field;
6670
}
@@ -113,9 +117,12 @@ void writeFields(Append writer) {
113117
allField.writeDebug(writer);
114118
}
115119
writer.eol();
120+
if (hasRaw) {
121+
writer.append(" private final JsonAdapter<String> rawAdapter;").eol();
122+
}
116123
Set<String> uniqueTypes = new HashSet<>();
117124
for (FieldReader allField : allFields) {
118-
if (allField.include()) {
125+
if (allField.include() && !allField.isRaw()) {
119126
if (uniqueTypes.add(allField.adapterShortType())) {
120127
allField.writeField(writer);
121128
}
@@ -126,9 +133,12 @@ void writeFields(Append writer) {
126133
}
127134

128135
void writeConstructor(Append writer) {
136+
if (hasRaw) {
137+
writer.append(" this.rawAdapter = jsonb.rawAdapter();").eol();
138+
}
129139
Set<String> uniqueTypes = new HashSet<>();
130140
for (FieldReader allField : allFields) {
131-
if (allField.include()) {
141+
if (allField.include() || !allField.isRaw()) {
132142
if (uniqueTypes.add(allField.adapterShortType())) {
133143
allField.writeConstructor(writer);
134144
}

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

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class FieldReader {
2222
private final boolean serialize;
2323
private final boolean deserialize;
2424
private final boolean unmapped;
25+
private final boolean raw;
2526

2627
private MethodReader setter;
2728
private MethodReader getter;
@@ -38,10 +39,16 @@ class FieldReader {
3839

3940
PropertyIgnoreReader ignoreReader = new PropertyIgnoreReader(element);
4041
this.unmapped = ignoreReader.unmapped();
42+
this.raw = ignoreReader.raw();
4143
this.serialize = ignoreReader.serialize();
4244
this.deserialize = ignoreReader.deserialize();
43-
44-
if (unmapped) {
45+
if (raw) {
46+
genericType = GenericType.parse("java.lang.String");
47+
adapterShortType = "JsonAdapter<String>";
48+
adapterFieldName = "rawAdapter";
49+
defaultValue = "null";
50+
primitive = false;
51+
} else if (unmapped) {
4552
genericType = GenericType.parse("java.lang.Object");
4653
adapterShortType = "JsonAdapter<Object>";
4754
adapterFieldName = "objectJsonAdapter";
@@ -70,6 +77,10 @@ String propertyName() {
7077
return propertyName;
7178
}
7279

80+
boolean isRaw() {
81+
return raw;
82+
}
83+
7384
boolean isUnmapped() {
7485
return unmapped;
7586
}
@@ -100,11 +111,13 @@ void addImports(Set<String> importTypes) {
100111
if (unmapped) {
101112
importTypes.add("java.util.*");
102113
}
103-
genericType.addImports(importTypes);
114+
if (!raw) {
115+
genericType.addImports(importTypes);
116+
}
104117
}
105118

106119
void cascadeTypes(Set<String> types) {
107-
if (!unmapped) {
120+
if (!raw && !unmapped) {
108121
String topType = genericType.topType();
109122
if (topType.equals("java.util.List") || topType.equals("java.util.Set")) {
110123
types.add(genericType.firstParamType());
@@ -165,8 +178,12 @@ void writeField(Append writer) {
165178
}
166179

167180
void writeConstructor(Append writer) {
168-
String asType = genericType.asTypeDeclaration();
169-
writer.append(" this.%s = jsonb.adapter(%s);", adapterFieldName, asType).eol();
181+
if (raw) {
182+
writer.append(" this.%s = jsonb.rawAdapter();", adapterFieldName).eol();
183+
} else {
184+
String asType = genericType.asTypeDeclaration();
185+
writer.append(" this.%s = jsonb.adapter(%s);", adapterFieldName, asType).eol();
186+
}
170187
}
171188

172189
void writeToJson(Append writer, String varName, String prefix) {

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ class PropertyIgnoreReader {
1010

1111
private static final String JSON_IGNORE = "io.avaje.jsonb.Json.Ignore";
1212
private static final String JSON_UNMAPPED = "io.avaje.jsonb.Json.Unmapped";
13+
private static final String JSON_RAW = "io.avaje.jsonb.Json.Raw";
1314

1415
private boolean unmapped;
16+
private boolean raw;
1517
private boolean ignoreSerialize;
1618
private boolean ignoreDeserialize;
1719

@@ -23,6 +25,10 @@ boolean unmapped() {
2325
return unmapped;
2426
}
2527

28+
boolean raw() {
29+
return raw;
30+
}
31+
2632
boolean serialize() {
2733
return !ignoreSerialize;
2834
}
@@ -38,6 +44,8 @@ void read(Element element) {
3844
for (AnnotationMirror mirror : element.getAnnotationMirrors()) {
3945
if (JSON_UNMAPPED.equals(mirror.getAnnotationType().toString())) {
4046
unmapped = true;
47+
} else if (JSON_RAW.equals(mirror.getAnnotationType().toString())) {
48+
raw = true;
4149
} else if (JSON_IGNORE.equals(mirror.getAnnotationType().toString())) {
4250
ignoreDeserialize = true;
4351
ignoreSerialize = true;

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.fasterxml.jackson.core.JsonParser;
44
import com.fasterxml.jackson.core.JsonToken;
5+
import com.fasterxml.jackson.core.TreeNode;
56
import io.avaje.jsonb.JsonIoException;
67
import io.avaje.jsonb.JsonReader;
78
import io.avaje.jsonb.spi.PropertyNames;
@@ -50,6 +51,16 @@ public void skipValue() {
5051
}
5152
}
5253

54+
@Override
55+
public String readRaw() {
56+
try {
57+
TreeNode tree = parser.getCodec().readTree(parser);
58+
return tree.toString();
59+
} catch (IOException e) {
60+
throw new JsonIoException(e);
61+
}
62+
}
63+
5364
@Override
5465
public void beginArray() {
5566
try {

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,20 @@ public void value(byte[] value) {
342342
}
343343
}
344344

345+
@Override
346+
public void rawValue(String value) {
347+
if (value == null) {
348+
nullValue();
349+
} else {
350+
try {
351+
writeDeferredName();
352+
generator.writeRaw(value);
353+
} catch (IOException e) {
354+
throw new JsonIoException(e);
355+
}
356+
}
357+
}
358+
345359
@Override
346360
public void writeNewLine() {
347361
try {

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ public byte[] readBinary() {
113113
return new byte[0];
114114
}
115115

116+
@Override
117+
public String readRaw() {
118+
throw new RuntimeException("Not Supported");
119+
}
120+
116121
@Override
117122
public boolean isNullValue() {
118123
return currenEvent == JsonParser.Event.VALUE_NULL;

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,12 +219,17 @@ public void value(BigInteger value) {
219219

220220
@Override
221221
public void value(byte[] value) {
222+
throw new RuntimeException("Not supported");
223+
}
222224

225+
@Override
226+
public void rawValue(String value) {
227+
throw new RuntimeException("Not supported");
223228
}
224229

225230
@Override
226231
public void writeNewLine() {
227-
// TODO: No support to write new line characters for x-json-stream
232+
throw new RuntimeException("Not supported");
228233
}
229234

230235
@Override

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,14 @@
182182
SubType[] value();
183183
}
184184

185+
/**
186+
* Marks a String field as containing raw JSON content.
187+
*/
188+
@Retention(CLASS)
189+
@Target({ElementType.FIELD})
190+
@interface Raw {
191+
192+
}
185193

186194
/**
187195
* The naming convention that we can use for a given type.

0 commit comments

Comments
 (0)