Skip to content

#39 - Adds support for types with generic parameters #43

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.example.customer.generics;

import io.avaje.jsonb.Json;

@Json
public class MyGenericHolder<T> {

String title;
String author;
T document;

public String getTitle() {
return title;
}

public MyGenericHolder<T> setTitle(String title) {
this.title = title;
return this;
}

public String getAuthor() {
return author;
}

public MyGenericHolder<T> setAuthor(String author) {
this.author = author;
return this;
}

public T getDocument() {
return document;
}

public MyGenericHolder<T> setDocument(T document) {
this.document = document;
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.example.customer.generics;

import io.avaje.jsonb.Json;

import java.util.List;

@Json
public class MyGenericPageResult<T> {

int page;
int pageSize;
int totalPageCount;
List<? extends T> results;

public int getPage() {
return page;
}

public MyGenericPageResult<T> setPage(int page) {
this.page = page;
return this;
}

public int getPageSize() {
return pageSize;
}

public MyGenericPageResult<T> setPageSize(int pageSize) {
this.pageSize = pageSize;
return this;
}

public int getTotalPageCount() {
return totalPageCount;
}

public MyGenericPageResult<T> setTotalPageCount(int totalPageCount) {
this.totalPageCount = totalPageCount;
return this;
}

public List<? extends T> getResults() {
return results;
}

public MyGenericPageResult<T> setResults(List<? extends T> results) {
this.results = results;
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package org.example.customer.generics;

import io.avaje.jsonb.JsonType;
import io.avaje.jsonb.JsonView;
import io.avaje.jsonb.Jsonb;
import io.avaje.jsonb.Types;
import org.example.customer.Address;
import org.junit.jupiter.api.Test;

import java.util.LinkedHashMap;

import static org.assertj.core.api.Assertions.assertThat;

class MyGenericHolderTest {

Jsonb jsonb = Jsonb.builder().build();

private static MyGenericHolder<Address> createTestData() {
var bean = new MyGenericHolder<Address>();
bean.setTitle("hello").setAuthor("art").setDocument(new Address(90L, "one"));
return bean;
}

@SuppressWarnings({"rawtypes"})
@Test
void toJson() {
MyGenericHolder<Address> bean = createTestData();

var type = jsonb.type(MyGenericHolder.class);

String asJson = type.toJson(bean);
assertThat(asJson).isEqualTo("{\"title\":\"hello\",\"author\":\"art\",\"document\":{\"id\":90,\"street\":\"one\"}}");
assertThat(jsonb.toJson(bean)).isEqualTo(asJson);

MyGenericHolder pageResult = type.fromJson(asJson);
Object document = pageResult.getDocument();
// reading via Object means the list contains LinkedHashMap
assertThat(document).isInstanceOf(LinkedHashMap.class);
LinkedHashMap asMap = (LinkedHashMap)document;
assertThat(asMap.get("street")).isEqualTo("one");

JsonView<MyGenericHolder> view = type.view("author,document(id)");
String partialJson2 = view.toJson(bean);
// not supporting partial on the generic object (output includes street)
assertThat(partialJson2).isEqualTo("{\"author\":\"art\",\"document\":{\"id\":90,\"street\":\"one\"}}");
}


@Test
void toJson_withGenericParam() {
MyGenericHolder<Address> bean = createTestData();

Jsonb jsonb = Jsonb.builder().build();
JsonType<MyGenericHolder<Address>> type = jsonb.type(Types.newParameterizedType(MyGenericHolder.class, Address.class));

String asJson = type.toJson(bean);
assertThat(asJson).isEqualTo("{\"title\":\"hello\",\"author\":\"art\",\"document\":{\"id\":90,\"street\":\"one\"}}");
assertThat(jsonb.toJson(bean)).isEqualTo(asJson);

MyGenericHolder<Address> genericResult = type.fromJson(asJson);
Address document = genericResult.getDocument();

assertThat(document.getId()).isEqualTo(90L);
assertThat(document.getStreet()).isEqualTo("one");


JsonView<MyGenericHolder<Address>> partial = type.view("author,document(*)");
String partialJson = partial.toJson(bean);
assertThat(partialJson).isEqualTo("{\"author\":\"art\",\"document\":{\"id\":90,\"street\":\"one\"}}");

JsonView<MyGenericHolder<Address>> partial2 = type.view("author,document(id)");
String partialJson2 = partial2.toJson(bean);
assertThat(partialJson2).isEqualTo("{\"author\":\"art\",\"document\":{\"id\":90}}");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package org.example.customer.generics;

import io.avaje.jsonb.JsonType;
import io.avaje.jsonb.JsonView;
import io.avaje.jsonb.Jsonb;
import io.avaje.jsonb.Types;
import org.example.customer.Address;
import org.junit.jupiter.api.Test;

import java.util.LinkedHashMap;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

class MyGenericPageResultTest {

Jsonb jsonb = Jsonb.builder().build();

private static MyGenericPageResult<Address> createPageTestData() {
var bean = new MyGenericPageResult<Address>();
bean.setPage(42).setPageSize(10).setResults(List.of(new Address(90L, "one"), new Address(91L, "two")));
return bean;
}

@SuppressWarnings({"rawtypes"})
@Test
void toJson() {
MyGenericPageResult<Address> bean = createPageTestData();

var type = jsonb.type(MyGenericPageResult.class);

String asJson = type.toJson(bean);
assertThat(asJson).isEqualTo("{\"page\":42,\"pageSize\":10,\"totalPageCount\":0,\"results\":[{\"id\":90,\"street\":\"one\"},{\"id\":91,\"street\":\"two\"}]}");
assertThat(jsonb.toJson(bean)).isEqualTo(asJson);

MyGenericPageResult pageResult = type.fromJson(asJson);
List list = pageResult.getResults();
assertThat(list).hasSize(2);
// reading via Object means the list contains LinkedHashMap
assertThat(list.get(0)).isInstanceOf(LinkedHashMap.class);

JsonView<MyGenericPageResult> partial2 = type.view("page,results(id)");
String partialJson2 = partial2.toJson(bean);
// not supporting partial on the generic list of object (output includes street)
assertThat(partialJson2).isEqualTo("{\"page\":42,\"results\":[{\"id\":90,\"street\":\"one\"},{\"id\":91,\"street\":\"two\"}]}");
}


@Test
void toJson_withGenericParam() {
MyGenericPageResult<Address> bean = createPageTestData();

Jsonb jsonb = Jsonb.builder().build();

JsonType<MyGenericPageResult<Address>> type = jsonb.type(Types.newParameterizedType(MyGenericPageResult.class, Address.class));

String asJson = type.toJson(bean);
assertThat(asJson).isEqualTo("{\"page\":42,\"pageSize\":10,\"totalPageCount\":0,\"results\":[{\"id\":90,\"street\":\"one\"},{\"id\":91,\"street\":\"two\"}]}");
assertThat(jsonb.toJson(bean)).isEqualTo(asJson);

MyGenericPageResult<Address> genericResult = type.fromJson(asJson);
List<? extends Address> addresses = genericResult.getResults();

assertThat(addresses).hasSize(2);
assertThat(addresses.get(0)).isInstanceOf(Address.class);

JsonView<MyGenericPageResult<Address>> partial = type.view("page,results(*)");
String partialJson = partial.toJson(bean);
assertThat(partialJson).isEqualTo("{\"page\":42,\"results\":[{\"id\":90,\"street\":\"one\"},{\"id\":91,\"street\":\"two\"}]}");

JsonView<MyGenericPageResult<Address>> partial2 = type.view("page,results(id)");
String partialJson2 = partial2.toJson(bean);
assertThat(partialJson2).isEqualTo("{\"page\":42,\"results\":[{\"id\":90},{\"id\":91}]}");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ public BeanReader(TypeElement beanType, TypeElement mixInElement, ProcessingCont
this.constructor = typeReader.constructor();
}

int genericTypeParamsCount() {
return typeReader.genericTypeParamsCount();
}

@Override
public String toString() {
return beanType.toString();
Expand Down Expand Up @@ -106,6 +110,10 @@ private String shortName(Element element) {
}

private Set<String> importTypes() {
if (genericTypeParamsCount() > 0) {
importTypes.add(Constants.REFLECT_TYPE);
importTypes.add(Constants.PARAMETERIZED_TYPE);
}
importTypes.add(Constants.JSONB_WILD);
importTypes.add(Constants.IOEXCEPTION);
importTypes.add(Constants.JSONB_SPI);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
class ComponentMetaData {

private final List<String> allTypes = new ArrayList<>();
private final List<String> factoryTypes = new ArrayList<>();
private String fullName;

@Override
Expand All @@ -27,6 +28,10 @@ void add(String type) {
allTypes.add(type);
}

void addFactory(String fullName) {
factoryTypes.add(fullName);
}

void setFullName(String fullName) {
this.fullName = fullName;
}
Expand All @@ -50,6 +55,10 @@ List<String> all() {
return allTypes;
}

List<String> allFactories() {
return factoryTypes;
}

/**
* Return the package imports for the JsonAdapters and related types.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
class ComponentReader {

private static final String META_DATA = "io.avaje.jsonb.spi.MetaData";
private static final String META_DATA_FACTORY = "io.avaje.jsonb.spi.MetaData.Factory";
private final ProcessingContext ctx;
private final ComponentMetaData componentMetaData;

Expand All @@ -44,13 +45,21 @@ void read() {
private void readMetaData(TypeElement moduleType) {
for (AnnotationMirror annotationMirror : moduleType.getAnnotationMirrors()) {
if (META_DATA.equals(annotationMirror.getAnnotationType().toString())) {
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : annotationMirror.getElementValues().entrySet()) {
for (Object adapterEntry : (List<?>) entry.getValue().getValue()) {
componentMetaData.add(adapterNameFromEntry(adapterEntry));
}
}
readValues(annotationMirror).forEach(componentMetaData::add);
} else if (META_DATA_FACTORY.equals(annotationMirror.getAnnotationType().toString())) {
readValues(annotationMirror).forEach(componentMetaData::addFactory);
}
}
}

private List<String> readValues(AnnotationMirror annotationMirror) {
List<String> adapterClasses = new ArrayList<>();
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : annotationMirror.getElementValues().entrySet()) {
for (Object adapterEntry : (List<?>) entry.getValue().getValue()) {
adapterClasses.add(adapterNameFromEntry(adapterEntry));
}
}
return adapterClasses;
}

private String adapterNameFromEntry(Object adapterEntry) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ class Constants {
static final String JSONB = "io.avaje.jsonb.Jsonb";
static final String IOEXCEPTION = "java.io.IOException";
static final String METHODHANDLE = "java.lang.invoke.MethodHandle";
static final String REFLECT_TYPE = "java.lang.reflect.Type";
static final String PARAMETERIZED_TYPE = "java.lang.reflect.ParameterizedType";

}
Loading