Skip to content

Commit b9f353e

Browse files
authored
Merge pull request #43 from avaje/feature/39-generic-params-support
#39 - Adds support for types with generic parameters
2 parents e3ee8a6 + 09c501e commit b9f353e

File tree

16 files changed

+471
-67
lines changed

16 files changed

+471
-67
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package org.example.customer.generics;
2+
3+
import io.avaje.jsonb.Json;
4+
5+
@Json
6+
public class MyGenericHolder<T> {
7+
8+
String title;
9+
String author;
10+
T document;
11+
12+
public String getTitle() {
13+
return title;
14+
}
15+
16+
public MyGenericHolder<T> setTitle(String title) {
17+
this.title = title;
18+
return this;
19+
}
20+
21+
public String getAuthor() {
22+
return author;
23+
}
24+
25+
public MyGenericHolder<T> setAuthor(String author) {
26+
this.author = author;
27+
return this;
28+
}
29+
30+
public T getDocument() {
31+
return document;
32+
}
33+
34+
public MyGenericHolder<T> setDocument(T document) {
35+
this.document = document;
36+
return this;
37+
}
38+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package org.example.customer.generics;
2+
3+
import io.avaje.jsonb.Json;
4+
5+
import java.util.List;
6+
7+
@Json
8+
public class MyGenericPageResult<T> {
9+
10+
int page;
11+
int pageSize;
12+
int totalPageCount;
13+
List<? extends T> results;
14+
15+
public int getPage() {
16+
return page;
17+
}
18+
19+
public MyGenericPageResult<T> setPage(int page) {
20+
this.page = page;
21+
return this;
22+
}
23+
24+
public int getPageSize() {
25+
return pageSize;
26+
}
27+
28+
public MyGenericPageResult<T> setPageSize(int pageSize) {
29+
this.pageSize = pageSize;
30+
return this;
31+
}
32+
33+
public int getTotalPageCount() {
34+
return totalPageCount;
35+
}
36+
37+
public MyGenericPageResult<T> setTotalPageCount(int totalPageCount) {
38+
this.totalPageCount = totalPageCount;
39+
return this;
40+
}
41+
42+
public List<? extends T> getResults() {
43+
return results;
44+
}
45+
46+
public MyGenericPageResult<T> setResults(List<? extends T> results) {
47+
this.results = results;
48+
return this;
49+
}
50+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package org.example.customer.generics;
2+
3+
import io.avaje.jsonb.JsonType;
4+
import io.avaje.jsonb.JsonView;
5+
import io.avaje.jsonb.Jsonb;
6+
import io.avaje.jsonb.Types;
7+
import org.example.customer.Address;
8+
import org.junit.jupiter.api.Test;
9+
10+
import java.util.LinkedHashMap;
11+
12+
import static org.assertj.core.api.Assertions.assertThat;
13+
14+
class MyGenericHolderTest {
15+
16+
Jsonb jsonb = Jsonb.builder().build();
17+
18+
private static MyGenericHolder<Address> createTestData() {
19+
var bean = new MyGenericHolder<Address>();
20+
bean.setTitle("hello").setAuthor("art").setDocument(new Address(90L, "one"));
21+
return bean;
22+
}
23+
24+
@SuppressWarnings({"rawtypes"})
25+
@Test
26+
void toJson() {
27+
MyGenericHolder<Address> bean = createTestData();
28+
29+
var type = jsonb.type(MyGenericHolder.class);
30+
31+
String asJson = type.toJson(bean);
32+
assertThat(asJson).isEqualTo("{\"title\":\"hello\",\"author\":\"art\",\"document\":{\"id\":90,\"street\":\"one\"}}");
33+
assertThat(jsonb.toJson(bean)).isEqualTo(asJson);
34+
35+
MyGenericHolder pageResult = type.fromJson(asJson);
36+
Object document = pageResult.getDocument();
37+
// reading via Object means the list contains LinkedHashMap
38+
assertThat(document).isInstanceOf(LinkedHashMap.class);
39+
LinkedHashMap asMap = (LinkedHashMap)document;
40+
assertThat(asMap.get("street")).isEqualTo("one");
41+
42+
JsonView<MyGenericHolder> view = type.view("author,document(id)");
43+
String partialJson2 = view.toJson(bean);
44+
// not supporting partial on the generic object (output includes street)
45+
assertThat(partialJson2).isEqualTo("{\"author\":\"art\",\"document\":{\"id\":90,\"street\":\"one\"}}");
46+
}
47+
48+
49+
@Test
50+
void toJson_withGenericParam() {
51+
MyGenericHolder<Address> bean = createTestData();
52+
53+
Jsonb jsonb = Jsonb.builder().build();
54+
JsonType<MyGenericHolder<Address>> type = jsonb.type(Types.newParameterizedType(MyGenericHolder.class, Address.class));
55+
56+
String asJson = type.toJson(bean);
57+
assertThat(asJson).isEqualTo("{\"title\":\"hello\",\"author\":\"art\",\"document\":{\"id\":90,\"street\":\"one\"}}");
58+
assertThat(jsonb.toJson(bean)).isEqualTo(asJson);
59+
60+
MyGenericHolder<Address> genericResult = type.fromJson(asJson);
61+
Address document = genericResult.getDocument();
62+
63+
assertThat(document.getId()).isEqualTo(90L);
64+
assertThat(document.getStreet()).isEqualTo("one");
65+
66+
67+
JsonView<MyGenericHolder<Address>> partial = type.view("author,document(*)");
68+
String partialJson = partial.toJson(bean);
69+
assertThat(partialJson).isEqualTo("{\"author\":\"art\",\"document\":{\"id\":90,\"street\":\"one\"}}");
70+
71+
JsonView<MyGenericHolder<Address>> partial2 = type.view("author,document(id)");
72+
String partialJson2 = partial2.toJson(bean);
73+
assertThat(partialJson2).isEqualTo("{\"author\":\"art\",\"document\":{\"id\":90}}");
74+
}
75+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package org.example.customer.generics;
2+
3+
import io.avaje.jsonb.JsonType;
4+
import io.avaje.jsonb.JsonView;
5+
import io.avaje.jsonb.Jsonb;
6+
import io.avaje.jsonb.Types;
7+
import org.example.customer.Address;
8+
import org.junit.jupiter.api.Test;
9+
10+
import java.util.LinkedHashMap;
11+
import java.util.List;
12+
13+
import static org.assertj.core.api.Assertions.assertThat;
14+
15+
class MyGenericPageResultTest {
16+
17+
Jsonb jsonb = Jsonb.builder().build();
18+
19+
private static MyGenericPageResult<Address> createPageTestData() {
20+
var bean = new MyGenericPageResult<Address>();
21+
bean.setPage(42).setPageSize(10).setResults(List.of(new Address(90L, "one"), new Address(91L, "two")));
22+
return bean;
23+
}
24+
25+
@SuppressWarnings({"rawtypes"})
26+
@Test
27+
void toJson() {
28+
MyGenericPageResult<Address> bean = createPageTestData();
29+
30+
var type = jsonb.type(MyGenericPageResult.class);
31+
32+
String asJson = type.toJson(bean);
33+
assertThat(asJson).isEqualTo("{\"page\":42,\"pageSize\":10,\"totalPageCount\":0,\"results\":[{\"id\":90,\"street\":\"one\"},{\"id\":91,\"street\":\"two\"}]}");
34+
assertThat(jsonb.toJson(bean)).isEqualTo(asJson);
35+
36+
MyGenericPageResult pageResult = type.fromJson(asJson);
37+
List list = pageResult.getResults();
38+
assertThat(list).hasSize(2);
39+
// reading via Object means the list contains LinkedHashMap
40+
assertThat(list.get(0)).isInstanceOf(LinkedHashMap.class);
41+
42+
JsonView<MyGenericPageResult> partial2 = type.view("page,results(id)");
43+
String partialJson2 = partial2.toJson(bean);
44+
// not supporting partial on the generic list of object (output includes street)
45+
assertThat(partialJson2).isEqualTo("{\"page\":42,\"results\":[{\"id\":90,\"street\":\"one\"},{\"id\":91,\"street\":\"two\"}]}");
46+
}
47+
48+
49+
@Test
50+
void toJson_withGenericParam() {
51+
MyGenericPageResult<Address> bean = createPageTestData();
52+
53+
Jsonb jsonb = Jsonb.builder().build();
54+
55+
JsonType<MyGenericPageResult<Address>> type = jsonb.type(Types.newParameterizedType(MyGenericPageResult.class, Address.class));
56+
57+
String asJson = type.toJson(bean);
58+
assertThat(asJson).isEqualTo("{\"page\":42,\"pageSize\":10,\"totalPageCount\":0,\"results\":[{\"id\":90,\"street\":\"one\"},{\"id\":91,\"street\":\"two\"}]}");
59+
assertThat(jsonb.toJson(bean)).isEqualTo(asJson);
60+
61+
MyGenericPageResult<Address> genericResult = type.fromJson(asJson);
62+
List<? extends Address> addresses = genericResult.getResults();
63+
64+
assertThat(addresses).hasSize(2);
65+
assertThat(addresses.get(0)).isInstanceOf(Address.class);
66+
67+
JsonView<MyGenericPageResult<Address>> partial = type.view("page,results(*)");
68+
String partialJson = partial.toJson(bean);
69+
assertThat(partialJson).isEqualTo("{\"page\":42,\"results\":[{\"id\":90,\"street\":\"one\"},{\"id\":91,\"street\":\"two\"}]}");
70+
71+
JsonView<MyGenericPageResult<Address>> partial2 = type.view("page,results(id)");
72+
String partialJson2 = partial2.toJson(bean);
73+
assertThat(partialJson2).isEqualTo("{\"page\":42,\"results\":[{\"id\":90},{\"id\":91}]}");
74+
}
75+
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ public BeanReader(TypeElement beanType, TypeElement mixInElement, ProcessingCont
5757
this.constructor = typeReader.constructor();
5858
}
5959

60+
int genericTypeParamsCount() {
61+
return typeReader.genericTypeParamsCount();
62+
}
63+
6064
@Override
6165
public String toString() {
6266
return beanType.toString();
@@ -106,6 +110,10 @@ private String shortName(Element element) {
106110
}
107111

108112
private Set<String> importTypes() {
113+
if (genericTypeParamsCount() > 0) {
114+
importTypes.add(Constants.REFLECT_TYPE);
115+
importTypes.add(Constants.PARAMETERIZED_TYPE);
116+
}
109117
importTypes.add(Constants.JSONB_WILD);
110118
importTypes.add(Constants.IOEXCEPTION);
111119
importTypes.add(Constants.JSONB_SPI);

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
class ComponentMetaData {
66

77
private final List<String> allTypes = new ArrayList<>();
8+
private final List<String> factoryTypes = new ArrayList<>();
89
private String fullName;
910

1011
@Override
@@ -27,6 +28,10 @@ void add(String type) {
2728
allTypes.add(type);
2829
}
2930

31+
void addFactory(String fullName) {
32+
factoryTypes.add(fullName);
33+
}
34+
3035
void setFullName(String fullName) {
3136
this.fullName = fullName;
3237
}
@@ -50,6 +55,10 @@ List<String> all() {
5055
return allTypes;
5156
}
5257

58+
List<String> allFactories() {
59+
return factoryTypes;
60+
}
61+
5362
/**
5463
* Return the package imports for the JsonAdapters and related types.
5564
*/

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

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
class ComponentReader {
2020

2121
private static final String META_DATA = "io.avaje.jsonb.spi.MetaData";
22+
private static final String META_DATA_FACTORY = "io.avaje.jsonb.spi.MetaData.Factory";
2223
private final ProcessingContext ctx;
2324
private final ComponentMetaData componentMetaData;
2425

@@ -44,13 +45,21 @@ void read() {
4445
private void readMetaData(TypeElement moduleType) {
4546
for (AnnotationMirror annotationMirror : moduleType.getAnnotationMirrors()) {
4647
if (META_DATA.equals(annotationMirror.getAnnotationType().toString())) {
47-
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : annotationMirror.getElementValues().entrySet()) {
48-
for (Object adapterEntry : (List<?>) entry.getValue().getValue()) {
49-
componentMetaData.add(adapterNameFromEntry(adapterEntry));
50-
}
51-
}
48+
readValues(annotationMirror).forEach(componentMetaData::add);
49+
} else if (META_DATA_FACTORY.equals(annotationMirror.getAnnotationType().toString())) {
50+
readValues(annotationMirror).forEach(componentMetaData::addFactory);
51+
}
52+
}
53+
}
54+
55+
private List<String> readValues(AnnotationMirror annotationMirror) {
56+
List<String> adapterClasses = new ArrayList<>();
57+
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : annotationMirror.getElementValues().entrySet()) {
58+
for (Object adapterEntry : (List<?>) entry.getValue().getValue()) {
59+
adapterClasses.add(adapterNameFromEntry(adapterEntry));
5260
}
5361
}
62+
return adapterClasses;
5463
}
5564

5665
private String adapterNameFromEntry(Object adapterEntry) {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,7 @@ class Constants {
88
static final String JSONB = "io.avaje.jsonb.Jsonb";
99
static final String IOEXCEPTION = "java.io.IOException";
1010
static final String METHODHANDLE = "java.lang.invoke.MethodHandle";
11+
static final String REFLECT_TYPE = "java.lang.reflect.Type";
12+
static final String PARAMETERIZED_TYPE = "java.lang.reflect.ParameterizedType";
1113

1214
}

0 commit comments

Comments
 (0)