Skip to content

Commit 2f3c6f9

Browse files
authored
Merge pull request #142 from avaje/feature/interface-implementation
Add support for Interface type fromJson() by specifying an implementation
2 parents dfdf860 + 6ae18f5 commit 2f3c6f9

File tree

14 files changed

+236
-16
lines changed

14 files changed

+236
-16
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package org.example.customer.iface;
2+
3+
public interface DIFace {
4+
5+
String one();
6+
7+
long two();
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package org.example.customer.iface;
2+
3+
public interface EIFace {
4+
5+
String one();
6+
7+
long two();
8+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.example.customer.iface.implementation;
2+
3+
import io.avaje.jsonb.Json;
4+
import org.example.customer.iface.DIFace;
5+
import org.example.customer.iface.EIFace;
6+
7+
@Json.Import(value = DIFace.class, implementation = MyDIFace.class)
8+
@Json.Import(value = EIFace.class)
9+
@Json
10+
public record MyDIFace(String one, long two) implements DIFace {
11+
12+
String banana() {
13+
return one;
14+
}
15+
}

blackbox-test/src/test/java/org/example/customer/iface/AIFaceTest.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.junit.jupiter.api.Test;
77

88
import static org.assertj.core.api.Assertions.assertThat;
9+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
910

1011
class AIFaceTest {
1112

@@ -37,4 +38,11 @@ void toJsonView() {
3738
assertThat(view2.toJson(new Foo("a", 42))).isEqualTo("{\"two\":42}");
3839
}
3940

41+
@Test
42+
void fromJson_expect_UnsupportedOperationException() {
43+
JsonType<AIFace> type = jsonb.type(AIFace.class);
44+
assertThatThrownBy(() -> {
45+
type.fromJson("{\"one\":\"a\",\"two\":42}");
46+
}).isInstanceOf(UnsupportedOperationException.class);
47+
}
4048
}

blackbox-test/src/test/java/org/example/customer/iface/BIFaceTest.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.junit.jupiter.api.Test;
77

88
import static org.assertj.core.api.Assertions.assertThat;
9+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
910

1011
class BIFaceTest {
1112

@@ -51,4 +52,12 @@ void toJsonView() {
5152
assertThat(view2.toJson(new Foo("a", true, "b", "c"))).isEqualTo("{\"two\":true}");
5253
}
5354

55+
@Test
56+
void fromJson_expect_UnsupportedOperationException() {
57+
JsonType<BIFace> type = jsonb.type(BIFace.class);
58+
assertThatThrownBy(() -> {
59+
type.fromJson("{\"one\":\"a\",\"two\":42}");
60+
}).isInstanceOf(UnsupportedOperationException.class);
61+
}
62+
5463
}

blackbox-test/src/test/java/org/example/customer/iface/CIFaceTest.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.junit.jupiter.api.Test;
77

88
import static org.assertj.core.api.Assertions.assertThat;
9+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
910

1011
class CIFaceTest {
1112

@@ -37,4 +38,12 @@ void toJsonView() {
3738
assertThat(view2.toJson(new Foo("a", 42, "b"))).isEqualTo("{\"two-not-here\":42}");
3839
}
3940

41+
@Test
42+
void fromJson_expect_UnsupportedOperationException() {
43+
JsonType<CIFace> type = jsonb.type(CIFace.class);
44+
assertThatThrownBy(() -> {
45+
type.fromJson("{\"one\":\"a\",\"two\":42}");
46+
}).isInstanceOf(UnsupportedOperationException.class);
47+
}
48+
4049
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package org.example.customer.iface;
2+
3+
import io.avaje.jsonb.JsonType;
4+
import io.avaje.jsonb.JsonView;
5+
import io.avaje.jsonb.Jsonb;
6+
import org.example.customer.iface.implementation.MyDIFace;
7+
import org.junit.jupiter.api.Test;
8+
9+
import static org.assertj.core.api.Assertions.assertThat;
10+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
11+
12+
class DIFaceTest {
13+
14+
Jsonb jsonb = Jsonb.builder().build();
15+
16+
record Foo(String one, long two) implements DIFace {
17+
}
18+
19+
@Test
20+
void toJson() {
21+
JsonType<DIFace> type = jsonb.type(DIFace.class);
22+
23+
String asJson = type.toJson(new Foo("a", 42));
24+
assertThat(asJson).isEqualTo("{\"one\":\"a\",\"two\":42}");
25+
}
26+
27+
@Test
28+
void toJsonView() {
29+
JsonType<DIFace> type = jsonb.type(DIFace.class);
30+
31+
JsonView<DIFace> view0 = type.view("(one)");
32+
String asJson = view0.toJson(new Foo("a", 42));
33+
assertThat(asJson).isEqualTo("{\"one\":\"a\"}");
34+
35+
JsonView<DIFace> view1 = type.view("(one,two)");
36+
assertThat(view1.toJson(new Foo("a", 42))).isEqualTo("{\"one\":\"a\",\"two\":42}");
37+
38+
JsonView<DIFace> view2 = type.view("(two)");
39+
assertThat(view2.toJson(new Foo("a", 42))).isEqualTo("{\"two\":42}");
40+
}
41+
42+
@Test
43+
void fromJson() {
44+
JsonType<DIFace> type = jsonb.type(DIFace.class);
45+
DIFace bean = type.fromJson("{\"one\":\"a\",\"two\":42}");
46+
47+
assertThat(bean.one()).isEqualTo("a");
48+
assertThat(bean.two()).isEqualTo(42L);
49+
assertThat(bean).isInstanceOf(MyDIFace.class);
50+
}
51+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package org.example.customer.iface;
2+
3+
import io.avaje.jsonb.JsonType;
4+
import io.avaje.jsonb.JsonView;
5+
import io.avaje.jsonb.Jsonb;
6+
import org.junit.jupiter.api.Test;
7+
8+
import java.io.IOException;
9+
10+
import static org.assertj.core.api.Assertions.assertThat;
11+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
12+
13+
class EIFaceTest {
14+
15+
Jsonb jsonb = Jsonb.builder().build();
16+
17+
record Foo(String one, long two) implements EIFace {
18+
}
19+
20+
@Test
21+
void toJson() {
22+
JsonType<EIFace> type = jsonb.type(EIFace.class);
23+
24+
String asJson = type.toJson(new Foo("a", 42));
25+
assertThat(asJson).isEqualTo("{\"one\":\"a\",\"two\":42}");
26+
}
27+
28+
@Test
29+
void toJsonView() {
30+
JsonType<EIFace> type = jsonb.type(EIFace.class);
31+
32+
JsonView<EIFace> view0 = type.view("(one)");
33+
String asJson = view0.toJson(new Foo("a", 42));
34+
assertThat(asJson).isEqualTo("{\"one\":\"a\"}");
35+
36+
JsonView<EIFace> view1 = type.view("(one,two)");
37+
assertThat(view1.toJson(new Foo("a", 42))).isEqualTo("{\"one\":\"a\",\"two\":42}");
38+
39+
JsonView<EIFace> view2 = type.view("(two)");
40+
assertThat(view2.toJson(new Foo("a", 42))).isEqualTo("{\"two\":42}");
41+
}
42+
43+
@Test
44+
void fromJson_expect_UnsupportedOperationException() {
45+
JsonType<EIFace> type = jsonb.type(EIFace.class);
46+
assertThatThrownBy(() -> {
47+
type.fromJson("{\"one\":\"a\",\"two\":42}");
48+
}).isInstanceOf(UnsupportedOperationException.class);
49+
}
50+
}

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

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ final class ClassReader implements BeanReader {
4040
private final Map<String, Boolean> isCommonFieldMap = new HashMap<>();
4141
private final boolean optional;
4242
private final List<TypeSubTypeMeta> subTypes;
43+
/** An Interface/abstract type with a single implementation */
44+
private ClassReader implementation;
4345

4446
ClassReader(TypeElement beanType) {
4547
this(beanType, null);
@@ -89,6 +91,13 @@ boolean isRecord(TypeElement beanType) {
8991
}
9092
}
9193

94+
/**
95+
* For an interface type set the single implementation to use for fromJson().
96+
*/
97+
void setImplementationType(TypeElement implementationType) {
98+
this.implementation = new ClassReader(implementationType);
99+
}
100+
92101
@Override
93102
public int genericTypeParamsCount() {
94103
return typeReader.genericTypeParamsCount();
@@ -164,9 +173,18 @@ private Set<String> importTypes() {
164173
for (final MethodProperty methodProperty : methodProperties) {
165174
methodProperty.addImports(importTypes);
166175
}
176+
if (implementation != null) {
177+
implementation.addImported(importTypes);
178+
}
167179
return importTypes;
168180
}
169181

182+
private void addImported(Set<String> importTypes) {
183+
if (Util.validImportType(type)) {
184+
importTypes.add(type);
185+
}
186+
}
187+
170188
@Override
171189
public void writeImports(Append writer) {
172190
for (final String importType : importTypes()) {
@@ -383,11 +401,18 @@ public void writeFromJson(Append writer) {
383401
writer.append(" @Override").eol();
384402
writer.append(" public %s fromJson(JsonReader reader) {", shortName, varName).eol();
385403
if (readOnlyInterface) {
386-
writer.append(" throw new UnsupportedOperationException();").eol();
387-
writer.append(" }").eol();
404+
if (implementation == null) {
405+
writer.append(" throw new UnsupportedOperationException();").eol();
406+
writer.append(" }").eol();
407+
} else {
408+
implementation.writeFromJsonImplementation(writer, varName);
409+
}
388410
return;
389411
}
412+
writeFromJsonImplementation(writer, varName);
413+
}
390414

415+
private void writeFromJsonImplementation(Append writer, String varName) {
391416
final boolean directLoad = (constructor == null && !hasSubTypes && !optional);
392417
if (directLoad) {
393418
// default public constructor

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ final class Constants {
88
static final String JSONB = "io.avaje.jsonb.Jsonb";
99
static final String JSON = "io.avaje.jsonb.Json";
1010
static final String JSON_IMPORT = "io.avaje.jsonb.Json.Import";
11+
static final String JSON_IMPORT_LIST = "io.avaje.jsonb.Json.Import.List";
1112
static final String JSON_MIXIN = "io.avaje.jsonb.Json.MixIn";
1213
static final String IOEXCEPTION = "java.io.IOException";
1314
static final String METHODHANDLE = "java.lang.invoke.MethodHandle";

0 commit comments

Comments
 (0)