Skip to content

Commit 3f6d7d4

Browse files
authored
More polymorphism TCKs added (#300)
- More polymorphism TCKs added - minor change in secure class deserialization Signed-off-by: David Kral <david.k.kral@oracle.com>
1 parent 34327df commit 3f6d7d4

File tree

7 files changed

+455
-68
lines changed

7 files changed

+455
-68
lines changed

api/src/main/java/jakarta/json/bind/annotation/JsonbPolymorphicType.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,28 +27,32 @@
2727
* This annotation is required on the most common parent of all classes when polymorphism will be applied.
2828
* <pre><code>
2929
* // Example
30-
* {@literal @}JsonbPolymorphicType(keyName = "@key")
30+
* {@literal @}JsonbPolymorphicType(key = "@key")
3131
* interface Animal {}
3232
*
3333
* class Dog implements Animal {}
3434
* class Cat implements Animal {}
3535
* </code></pre>
3636
* This annotation is tightly bound to {@link JsonbSubtype}. It is recommended to use
3737
* {@link JsonbSubtype} annotations to specify all the possible classes and their aliases.
38-
* <br>
3938
*/
4039
@JsonbAnnotation
4140
@Retention(RetentionPolicy.RUNTIME)
4241
@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE})
4342
public @interface JsonbPolymorphicType {
4443

44+
/**
45+
* Default polymorphic information key name.
46+
*/
47+
String DEFAULT_KEY_NAME = "@type";
48+
4549
/**
4650
* Key used for keeping polymorphic information when {@link Format#PROPERTY} is chosen.
4751
* Default value is {@code @type}.
4852
*
4953
* @return key name
5054
*/
51-
String key() default "";
55+
String key() default DEFAULT_KEY_NAME;
5256

5357
/**
5458
* Specification of how the polymorphic information will be stored in the resulting JSON.
@@ -70,12 +74,10 @@
7074
* Allowed package names. This option is ignored if {@link JsonbPolymorphicType#classNames()}
7175
* option is set to false.
7276
* <br>
73-
* Only classes contained in the selected packages will be serialized/deserialized.
77+
* Only classes contained in the selected packages will be deserialized.
7478
* Classes with specified alias are not validated.
7579
* <br>
76-
* It is strongly recommended that you set up allowed packages when classes without aliases will be processed.
77-
* <br>
78-
* When no package is specified, all classes without aliases are allowed.
80+
* It is required to have allowed packages set up when classes without aliases will be processed.
7981
*
8082
* @return list of allowed packages
8183
*/

spec/src/main/asciidoc/jsonb.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ Deserialization into anonymous classes is not supported. Serialization of anonym
320320

321321
Polymorphic type handling is supported for deserialization and serialization. Polymorphic handling is ensured by annotation `@JsonbPolymorphicType` and `@JsonbSubtype`. `@JsonbPolymorphicType` defines how the overall polymorphic information strategy will be serialized/deserialized and defines all the supported aliases using `@JsonbSubtype` annotations. `@JsonbSubtype` ensures proper and safe mapping between class alias and type. Implementation must validate mapped types if they are assignable from the annotated type. If not, an exception must be thrown.
322322

323-
Polymorphic information is obtained from `@JsonbSubtype` annotation as a type alias mapped to the type or if `classNames` property of the `@JsonbPolymorphicType` is enabled, it corresponds to the fully qualified class name, if no suitable alias is found. Implementation must ensure to support `allowedPackages` option of the `@JsonbPolymorphicType` which is only used when `classNames` option is enabled, it is ignored otherwise. It is not allowed to create instances of the classes outside the allowed packages. In such cases an exception must be thrown. If allowed packages are empty, all classes are allowed.
323+
Polymorphic information is obtained from `@JsonbSubtype` annotation as a type alias mapped to the type or if `classNames` property of the `@JsonbPolymorphicType` is enabled, it corresponds to the fully qualified class name, if no suitable alias is found. Implementation must ensure to support `allowedPackages` option of the `@JsonbPolymorphicType` which is only used when `classNames` option is enabled, it is ignored otherwise. It is not allowed to create instances of the classes outside the allowed packages. In such cases an exception must be thrown. It is also required to have `allowedPackages` option set if `classNames` option is enabled. If the `allowedPackages` is null or empty an exception must be thrown.
324324

325325
Available serialization formats:
326326

tck/src/main/java/jakarta/json/bind/tck/defaultmapping/polymorphictypes/AnnotationPolymorphismObjectTest.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@
3535
import org.junit.Test;
3636
import org.junit.runner.RunWith;
3737

38+
import static jakarta.json.bind.tck.RegexMatcher.matches;
39+
40+
import static org.hamcrest.CoreMatchers.instanceOf;
41+
import static org.hamcrest.CoreMatchers.is;
42+
import static org.hamcrest.MatcherAssert.assertThat;
43+
import static org.junit.Assert.assertThrows;
3844
import static org.junit.Assert.fail;
3945

4046
/**
@@ -153,6 +159,32 @@ public void testArrayDeserialization() {
153159
}
154160
}
155161

162+
@Test
163+
public void testSerializationClassNamesWithCorrectAllowedPackages() {
164+
String expected = "\\{\\s*\"jakarta.json.bind.tck.defaultmapping.polymorphictypes."
165+
+ "AnnotationPolymorphismObjectTest\\$ChildClassNamesWithCorrectAllowed\"\\s*:\\s*\\"
166+
+ "{\\s*\"parent\"\\s*:\\s*1\\s*,"
167+
+ "\\s*\"child\"\\s*:\\s*2\\s*\\}\\}";
168+
assertThat(jsonb.toJson(new ChildClassNamesWithCorrectAllowed()), matches(expected));
169+
}
170+
171+
@Test
172+
public void testDeserializationClassNamesWithCorrectAllowedPackages() {
173+
String json = "{\"jakarta.json.bind.tck.defaultmapping.polymorphictypes."
174+
+ "AnnotationPolymorphismObjectTest$ChildClassNamesWithCorrectAllowed\":{\"parent\":3,\"child\":4}}";
175+
ParentClassNamesWithCorrectAllowed deserialized = jsonb.fromJson(json, ParentClassNamesWithCorrectAllowed.class);
176+
assertThat(deserialized, instanceOf(ChildClassNamesWithCorrectAllowed.class));
177+
assertThat(deserialized.parent, is(3));
178+
assertThat(((ChildClassNamesWithCorrectAllowed)deserialized).child, is(4));
179+
}
180+
181+
@Test
182+
public void testDeserializationClassNamesWithIncorrectAllowedPackages() {
183+
String json = "{\"jakarta.json.bind.tck.defaultmapping.polymorphictypes."
184+
+ "AnnotationPolymorphismObjectTest$ChildClassNamesWithIncorrectAllowed\":{\"parent\":1,\"child\":2}}";
185+
assertThrows(JsonbException.class, () -> jsonb.fromJson(json, ParentClassNamesWithIncorrectAllowed.class));
186+
}
187+
156188
@JsonbPolymorphicType(format = JsonbPolymorphicType.Format.WRAPPING_OBJECT, value = {
157189
@JsonbSubtype(alias = "dog", type = Dog.class),
158190
@JsonbSubtype(alias = "cat", type = Cat.class)
@@ -197,4 +229,24 @@ public DateConstructor(@JsonbProperty("localDate") @JsonbDateFormat(value = "dd-
197229

198230
}
199231

232+
@JsonbPolymorphicType(format = JsonbPolymorphicType.Format.WRAPPING_OBJECT,
233+
classNames = true, allowedPackages = {"jakarta.json.bind.tck.defaultmapping.polymorphictypes"})
234+
public static class ParentClassNamesWithCorrectAllowed {
235+
public int parent = 1;
236+
}
237+
238+
public static class ChildClassNamesWithCorrectAllowed extends ParentClassNamesWithCorrectAllowed {
239+
public int child = 2;
240+
}
241+
242+
@JsonbPolymorphicType(format = JsonbPolymorphicType.Format.WRAPPING_OBJECT,
243+
classNames = true, allowedPackages = {"jakarta.jsonb.incorrect"})
244+
public static class ParentClassNamesWithIncorrectAllowed {
245+
public int parent = 1;
246+
}
247+
248+
public static class ChildClassNamesWithIncorrectAllowed extends ParentClassNamesWithIncorrectAllowed {
249+
public int child = 2;
250+
}
251+
200252
}

tck/src/main/java/jakarta/json/bind/tck/defaultmapping/polymorphictypes/AnnotationPolymorphismTest.java

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,9 @@
1616

1717
package jakarta.json.bind.tck.defaultmapping.polymorphictypes;
1818

19-
import java.io.ByteArrayInputStream;
2019
import java.lang.invoke.MethodHandles;
21-
import java.nio.charset.StandardCharsets;
2220
import java.time.LocalDate;
2321

24-
import jakarta.json.Json;
25-
import jakarta.json.JsonObject;
2622
import jakarta.json.bind.Jsonb;
2723
import jakarta.json.bind.JsonbBuilder;
2824
import jakarta.json.bind.JsonbException;
@@ -39,6 +35,12 @@
3935
import org.junit.Test;
4036
import org.junit.runner.RunWith;
4137

38+
import static jakarta.json.bind.tck.RegexMatcher.matches;
39+
40+
import static org.hamcrest.CoreMatchers.instanceOf;
41+
import static org.hamcrest.CoreMatchers.is;
42+
import static org.hamcrest.MatcherAssert.assertThat;
43+
import static org.junit.Assert.assertThrows;
4244
import static org.junit.Assert.fail;
4345

4446
/**
@@ -59,8 +61,6 @@ public static WebArchive createTestArchive() {
5961
public void testBasicSerialization() {
6062
Dog dog = new Dog();
6163
String jsonString = jsonb.toJson(dog);
62-
JsonObject object = Json.createParser(new ByteArrayInputStream(jsonString.getBytes(StandardCharsets.UTF_8))).getObject();
63-
6464
if (!jsonString.matches("\\{\\s*\"@type\"\\s*:\\s*\"dog\"\\s*,\\s*\"isDog\"\\s*:\\s*true\\s*\\}")) {
6565
fail("Failed to serialize Dog class correctly in the polymorphic format: "
6666
+ JsonbPolymorphicType.Format.PROPERTY.name());
@@ -160,6 +160,32 @@ public void testArrayDeserialization() {
160160
}
161161
}
162162

163+
@Test
164+
public void testSerializationClassNamesWithCorrectAllowedPackages() {
165+
String expected = "\\{\\s*\"@type\"\\s*:\\s*\"jakarta.json.bind.tck.defaultmapping.polymorphictypes"
166+
+ ".AnnotationPolymorphismTest\\$ChildClassNamesWithCorrectAllowed\"\\s*,"
167+
+ "\\s*\"parent\"\\s*:\\s*1\\s*,"
168+
+ "\\s*\"child\"\\s*:\\s*2\\s*\\}";
169+
assertThat(jsonb.toJson(new ChildClassNamesWithCorrectAllowed()), matches(expected));
170+
}
171+
172+
@Test
173+
public void testDeserializationClassNamesWithCorrectAllowedPackages() {
174+
String json = "{\"@type\":\"jakarta.json.bind.tck.defaultmapping.polymorphictypes"
175+
+ ".AnnotationPolymorphismTest$ChildClassNamesWithCorrectAllowed\",\"parent\":3,\"child\":4}";
176+
ParentClassNamesWithCorrectAllowed deserialized = jsonb.fromJson(json, ParentClassNamesWithCorrectAllowed.class);
177+
assertThat(deserialized, instanceOf(ChildClassNamesWithCorrectAllowed.class));
178+
assertThat(deserialized.parent, is(3));
179+
assertThat(((ChildClassNamesWithCorrectAllowed)deserialized).child, is(4));
180+
}
181+
182+
@Test
183+
public void testDeserializationClassNamesWithIncorrectAllowedPackages() {
184+
String json = "{\"@type\":\"jakarta.json.bind.tck.defaultmapping.polymorphictypes."
185+
+ "AnnotationPolymorphismTest$ChildClassNamesWithIncorrectAllowed\",\"parent\":1,\"child\":2}";
186+
assertThrows(JsonbException.class, () -> jsonb.fromJson(json, ParentClassNamesWithIncorrectAllowed.class));
187+
}
188+
163189
@JsonbPolymorphicType({
164190
@JsonbSubtype(alias = "dog", type = Dog.class),
165191
@JsonbSubtype(alias = "cat", type = Cat.class),
@@ -212,4 +238,22 @@ public DateConstructor(@JsonbProperty("localDate") @JsonbDateFormat(value = "dd-
212238

213239
}
214240

241+
@JsonbPolymorphicType(classNames = true, allowedPackages = {"jakarta.json.bind.tck.defaultmapping.polymorphictypes"})
242+
public static class ParentClassNamesWithCorrectAllowed {
243+
public int parent = 1;
244+
}
245+
246+
public static class ChildClassNamesWithCorrectAllowed extends ParentClassNamesWithCorrectAllowed {
247+
public int child = 2;
248+
}
249+
250+
@JsonbPolymorphicType(classNames = true, allowedPackages = {"jakarta.jsonb.incorrect"})
251+
public static class ParentClassNamesWithIncorrectAllowed {
252+
public int parent = 1;
253+
}
254+
255+
public static class ChildClassNamesWithIncorrectAllowed extends ParentClassNamesWithIncorrectAllowed {
256+
public int child = 2;
257+
}
258+
215259
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0, which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* This Source Code may also be made available under the following Secondary
9+
* Licenses when the conditions for such availability set forth in the
10+
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
11+
* version 2 with the GNU Classpath Exception, which is available at
12+
* https://www.gnu.org/software/classpath/license.html.
13+
*
14+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15+
*/
16+
17+
package jakarta.json.bind.tck.defaultmapping.polymorphictypes;
18+
19+
import java.lang.invoke.MethodHandles;
20+
21+
import jakarta.json.bind.Jsonb;
22+
import jakarta.json.bind.JsonbBuilder;
23+
import jakarta.json.bind.annotation.JsonbPolymorphicType;
24+
import jakarta.json.bind.annotation.JsonbSubtype;
25+
26+
import org.jboss.arquillian.container.test.api.Deployment;
27+
import org.jboss.arquillian.junit.Arquillian;
28+
import org.jboss.shrinkwrap.api.ShrinkWrap;
29+
import org.jboss.shrinkwrap.api.spec.WebArchive;
30+
import org.junit.Test;
31+
import org.junit.runner.RunWith;
32+
33+
import static jakarta.json.bind.tck.RegexMatcher.matches;
34+
35+
import static org.hamcrest.CoreMatchers.instanceOf;
36+
import static org.hamcrest.MatcherAssert.assertThat;
37+
38+
@RunWith(Arquillian.class)
39+
public class MultiplePolymorphicInfoObjectTest {
40+
41+
private static final Jsonb JSONB = JsonbBuilder.create();
42+
43+
@Deployment
44+
public static WebArchive createTestArchive() {
45+
return ShrinkWrap.create(WebArchive.class)
46+
.addPackages(true, MethodHandles.lookup().lookupClass().getPackage().getName());
47+
}
48+
49+
@Test
50+
public void testMultiplePolymorphicInfoObjectSerialization() {
51+
String expected = "\\{\\s*\"animal\"\\s*:\\s*"
52+
+ "\\{\\s*\"dog\"\\s*:\\s*"
53+
+ "\\{\\s*\"labrador\"\\s*:\\s*"
54+
+ "\\{\\s*\"isLabrador\"\\s*:\\s*true\\s*\\}"
55+
+ "\\}\\}\\}";
56+
Labrador labrador = new Labrador();
57+
assertThat(JSONB.toJson(labrador), matches(expected));
58+
}
59+
60+
@Test
61+
public void testMultiplePolymorphicInfoObjectDeserialization() {
62+
String json = "{\"animal\":{\"dog\":{\"labrador\":{\"isLabrador\":true}}}}";
63+
assertThat(JSONB.fromJson(json, Labrador.class), instanceOf(Labrador.class));
64+
}
65+
66+
@Test
67+
public void testSerializeMultiplePolyTypesInSingleChain() {
68+
String expected = "\\{\\s*\"vehicle\"\\s*:\\s*"
69+
+ "\\{\\s*\"car\"\\s*:\\s*"
70+
+ "\\{"
71+
+ "\\s*\"machineProperty\"\\s*:\\s*\"machineProperty\"\\s*,"
72+
+ "\\s*\"vehicleProperty\"\\s*:\\s*\"vehicleProperty\"\\s*,"
73+
+ "\\s*\"carProperty\"\\s*:\\s*\"carProperty\"\\s*"
74+
+ "\\}\\}\\}";
75+
Car car = new Car();
76+
assertThat(JSONB.toJson(car), matches(expected));
77+
}
78+
79+
@Test
80+
public void testDeserializeMultiplePolyTypesInSingleChain() {
81+
String json = "{\"vehicle\":{\"car\":{"
82+
+ "\"machineProperty\":\"machineProperty\","
83+
+ "\"vehicleProperty\":\"vehicleProperty\","
84+
+ "\"carProperty\":\"carProperty\""
85+
+ "}}}";
86+
Machine machine = JSONB.fromJson(json, Machine.class);
87+
assertThat(machine, instanceOf(Car.class));
88+
}
89+
90+
@JsonbPolymorphicType(format = JsonbPolymorphicType.Format.WRAPPING_OBJECT, value = {
91+
@JsonbSubtype(alias = "animal", type = Animal.class)
92+
})
93+
public interface livingThing { }
94+
95+
@JsonbPolymorphicType(format = JsonbPolymorphicType.Format.WRAPPING_OBJECT, value = {
96+
@JsonbSubtype(alias = "dog", type = Dog.class)
97+
})
98+
public interface Animal extends livingThing { }
99+
100+
@JsonbPolymorphicType(format = JsonbPolymorphicType.Format.WRAPPING_OBJECT, value = {
101+
@JsonbSubtype(alias = "labrador", type = Labrador.class)
102+
})
103+
public interface Dog extends Animal { }
104+
105+
public static class Labrador implements Dog {
106+
107+
public boolean isLabrador = true;
108+
109+
}
110+
111+
@JsonbPolymorphicType(format = JsonbPolymorphicType.Format.WRAPPING_OBJECT, value = {
112+
@JsonbSubtype(alias = "vehicle", type = Vehicle.class)
113+
})
114+
public static class Machine {
115+
public String machineProperty = "machineProperty";
116+
}
117+
118+
@JsonbPolymorphicType(format = JsonbPolymorphicType.Format.WRAPPING_OBJECT, value = {
119+
@JsonbSubtype(alias = "car", type = Car.class)
120+
})
121+
public static class Vehicle extends Machine {
122+
public String vehicleProperty = "vehicleProperty";
123+
}
124+
125+
public static class Car extends Vehicle {
126+
public String carProperty = "carProperty";
127+
}
128+
129+
}

0 commit comments

Comments
 (0)