Skip to content

Commit faea958

Browse files
authored
Fix trailing comma in properties call for empty subtypes (#412)
* Fix trailing comma in properties call for empty subtypes Fixes code generation bug where adapters for sealed interfaces with empty subtypes generated invalid Java syntax: jsonb.properties("@type", ) - Refactor property name handling to use LinkedHashSet to avoid duplicates - Convert to stream-based approach for cleaner code - Add test case for empty sealed interface subtypes Fixes #410
1 parent f822e1d commit faea958

File tree

4 files changed

+109
-31
lines changed

4 files changed

+109
-31
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package org.example.customer.subtype;
2+
3+
import org.example.customer.subtype.Dessendre.Alicia;
4+
import org.example.customer.subtype.Dessendre.Curator;
5+
import org.example.customer.subtype.Dessendre.Paintress;
6+
7+
import io.avaje.jsonb.Json.SubType;
8+
9+
@io.avaje.jsonb.Json
10+
@SubType(type = Alicia.class, name = "Maelle")
11+
@SubType(type = Paintress.class, name = "Aline")
12+
@SubType(type = Curator.class, name = "Renoir")
13+
public sealed interface Dessendre {
14+
public record Alicia() implements Dessendre {}
15+
16+
public record Paintress() implements Dessendre {}
17+
18+
public record Curator() implements Dessendre {}
19+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.example.customer.subtype;
2+
3+
import io.avaje.jsonb.Json;
4+
5+
@Json
6+
@Json.SubType(type = EmptySupertype.SubtypeA.class, name = "a")
7+
@Json.SubType(type = EmptySupertype.SubtypeB.class, name = "b")
8+
public sealed interface EmptySupertype {
9+
record SubtypeA() implements EmptySupertype {}
10+
record SubtypeB() implements EmptySupertype {}
11+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package org.example.customer.subtype;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
6+
import org.junit.jupiter.api.Test;
7+
8+
import io.avaje.json.JsonAdapter;
9+
import io.avaje.jsonb.JsonType;
10+
import io.avaje.jsonb.Jsonb;
11+
12+
class EmptySupertypeTest {
13+
Jsonb jsonb = Jsonb.builder().build();
14+
JsonAdapter<EmptySupertype> adapter = jsonb.adapter(EmptySupertype.class);
15+
JsonType<EmptySupertype> type = jsonb.type(EmptySupertype.class);
16+
17+
@Test
18+
void nullObject() {
19+
var object = (EmptySupertype) null;
20+
var expected = "";
21+
var actual = type.toJson(object);
22+
assertEquals(expected, actual);
23+
}
24+
25+
@Test
26+
void objectA() {
27+
var object = new EmptySupertype.SubtypeA();
28+
var expected = "{\"@type\":\"a\"}";
29+
var actual = type.toJson(object);
30+
assertEquals(expected, actual);
31+
}
32+
33+
@Test
34+
void jsonA() {
35+
var json = "{\"@type\":\"a\"}";
36+
var expected = new EmptySupertype.SubtypeA();
37+
var actual = type.fromJson(json);
38+
assertEquals(expected, actual);
39+
}
40+
41+
@Test
42+
void objectB() {
43+
var object = new EmptySupertype.SubtypeB();
44+
var expected = "{\"@type\":\"b\"}";
45+
var actual = type.toJson(object);
46+
assertEquals(expected, actual);
47+
}
48+
49+
@Test
50+
void jsonB() {
51+
var json = "{\"@type\":\"b\"}";
52+
var expected = new EmptySupertype.SubtypeB();
53+
var actual = type.fromJson(json);
54+
assertEquals(expected, actual);
55+
}
56+
57+
@Test
58+
void invalidJsonC() {
59+
var json = "{\"@type\":\"c\"}";
60+
assertThrows(IllegalStateException.class, () -> type.fromJson(json));
61+
}
62+
}

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

Lines changed: 17 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import java.lang.reflect.InvocationTargetException;
99
import java.util.*;
10+
import java.util.stream.Collectors;
1011

1112
import javax.lang.model.element.Element;
1213
import javax.lang.model.element.ElementKind;
@@ -291,47 +292,32 @@ public void writeConstructor(Append writer) {
291292
}
292293
}
293294
writer.append(" this.names = jsonb.properties(");
295+
var properties = new LinkedHashSet<String>();
294296
if (hasSubTypes) {
295-
writer.append("\"").append(typeProperty).append("\", ");
297+
properties.add('"' + typeProperty + '"');
296298
}
297-
writer.append(propertyNames());
299+
properties.addAll(propertyNames());
300+
writer.append(String.join(", ", properties));
298301
writer.append(");").eol();
299302
}
300303

301-
private String propertyNames() {
304+
private List<String> propertyNames() {
302305
return readOnlyInterface ? propertyNamesReadOnly() : propertyNamesFields();
303306
}
304307

305-
private String propertyNamesFields() {
306-
final StringBuilder builder = new StringBuilder();
307-
//set to prevent writing same key twice
308-
final var seen = new HashSet<String>();
309-
for (int i = 0, size = allFields.size(); i < size; i++) {
310-
final FieldReader fieldReader = allFields.get(i);
311-
if (!seen.add(fieldReader.propertyName())) {
312-
continue;
313-
}
314-
if (i > 0) {
315-
builder.append(", ");
316-
}
317-
if (usesTypeProperty && fieldReader.propertyName().equals(typePropertyKey())) {
318-
builder.append(" ");
319-
continue;
320-
}
321-
builder.append("\"").append(fieldReader.propertyName()).append("\"");
322-
}
323-
return builder.toString().replace(" , ", "");
308+
private List<String> propertyNamesFields() {
309+
return allFields.stream()
310+
.map(FieldReader::propertyName)
311+
.map(property -> '"' + property + '"')
312+
.filter(property -> !usesTypeProperty || !property.equals(typePropertyKey()))
313+
.collect(toList());
324314
}
325315

326-
private String propertyNamesReadOnly() {
327-
final StringBuilder builder = new StringBuilder();
328-
for (int i = 0; i < methodProperties.size(); i++) {
329-
if (i > 0) {
330-
builder.append(", ");
331-
}
332-
builder.append("\"").append(methodProperties.get(i).propertyName()).append("\"");
333-
}
334-
return builder.toString().replace(" , ", "");
316+
private List<String> propertyNamesReadOnly() {
317+
return methodProperties.stream()
318+
.map(MethodProperty::propertyName)
319+
.map(property -> '"' + property + '"')
320+
.collect(toList());
335321
}
336322

337323
@Override

0 commit comments

Comments
 (0)