Skip to content

Commit dd5d023

Browse files
committed
Allow type coercion when determining APi types
When we read objects from the API, we want to ensure the type matches either the type class that the client is aware of, or whatever the complexType property on the object tells us. In some cases, the API returns a type for a property that is not the same or a subtype of the type defined by the API. The API should not do this, however the previous behavior was to throw an exception in these cases, which is quite annoying. Instead, we will now coerce the object we get into the type that the definition says. If properties are missing, they will remain unset, and any extra properties will be added to the unknown properties.
1 parent e0d978c commit dd5d023

File tree

4 files changed

+87
-25
lines changed

4 files changed

+87
-25
lines changed

src/main/java/com/softlayer/api/json/GsonJsonMarshallerFactory.java

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,8 @@
1414
import java.text.DateFormat;
1515
import java.text.ParseException;
1616
import java.text.SimpleDateFormat;
17-
import java.util.ArrayList;
18-
import java.util.Base64;
19-
import java.util.GregorianCalendar;
20-
import java.util.HashMap;
21-
import java.util.List;
22-
import java.util.Map;
17+
import java.util.*;
18+
import java.util.function.Supplier;
2319

2420
import com.google.gson.Gson;
2521
import com.google.gson.GsonBuilder;
@@ -191,12 +187,12 @@ public Entity read(JsonReader in) throws IOException {
191187
// we're an adapter for. So if we have SoftLayer_Something and a newer release of the
192188
// API has a type extending it but we don't have a generated class for it, it will get
193189
// properly serialized to a SoftLayer_Something.
190+
// If the API returns a type that isn't the same or a subtype of the type class,
191+
// try as best we can to fit the data within the type class.
194192
Class<? extends Entity> clazz = typeClasses.get(apiTypeName);
195193
Entity result;
196-
if (clazz == null) {
194+
if (clazz == null || !typeClass.isAssignableFrom(clazz)) {
197195
result = readForThisType(in);
198-
} else if (!typeClass.isAssignableFrom(clazz)) {
199-
throw new RuntimeException("Expecting " + typeClass + " to be super type of " + clazz);
200196
} else {
201197
result = ((EntityTypeAdapter) gson.getAdapter(clazz)).readForThisType(in);
202198
}

src/test/java/com/softlayer/api/json/GsonJsonMarshallerFactoryTest.java

Lines changed: 80 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.google.gson.reflect.TypeToken;
2222
import com.softlayer.api.service.Entity;
2323
import com.softlayer.api.service.TestEntity;
24+
import com.softlayer.api.service.TestThing;
2425

2526
public class GsonJsonMarshallerFactoryTest {
2627

@@ -51,21 +52,22 @@ private String toJson(Object obj) throws Exception {
5152
public void testRead() throws Exception {
5253
Entity entity = fromJson(Entity.class,
5354
"{"
54-
+ "\"complexType\": \"SoftLayer_TestEntity\","
55-
+ "\"bar\": \"some string\","
56-
+ "\"foo\": \"another string\","
57-
+ "\"baz\": null,"
58-
+ "\"date\": \"1984-02-25T20:15:25-06:00\","
59-
+ "\"notApiProperty\": \"bad value\","
60-
+ "\"child\": {"
61-
+ " \"complexType\": \"SoftLayer_TestEntity\","
62-
+ " \"bar\": \"child string\""
63-
+ "},"
64-
+ "\"moreChildren\": ["
65-
+ " { \"complexType\": \"SoftLayer_TestEntity\", \"bar\": \"child 1\" },"
66-
+ " { \"complexType\": \"SoftLayer_TestEntity\", \"bar\": \"child 2\" }"
67-
+ "]"
68-
+ "}");
55+
+ "\"complexType\": \"SoftLayer_TestEntity\","
56+
+ "\"bar\": \"some string\","
57+
+ "\"foo\": \"another string\","
58+
+ "\"baz\": null,"
59+
+ "\"date\": \"1984-02-25T20:15:25-06:00\","
60+
+ "\"notApiProperty\": \"bad value\","
61+
+ "\"child\": {"
62+
+ " \"complexType\": \"SoftLayer_TestEntity\","
63+
+ " \"bar\": \"child string\""
64+
+ "},"
65+
+ "\"moreChildren\": ["
66+
+ " { \"complexType\": \"SoftLayer_TestEntity\", \"bar\": \"child 1\" },"
67+
+ " { \"complexType\": \"SoftLayer_TestEntity\", \"bar\": \"child 2\" }"
68+
+ "],"
69+
+ "\"testThing\": {\"complexType\": \"SoftLayer_TestThing\", \"id\": 123}"
70+
+ "}");
6971
assertEquals(TestEntity.class, entity.getClass());
7072
TestEntity obj = (TestEntity) entity;
7173
assertEquals("some string", obj.getFoo());
@@ -85,6 +87,69 @@ public void testRead() throws Exception {
8587
assertEquals(2, obj.getMoreChildren().size());
8688
assertEquals("child 1", obj.getMoreChildren().get(0).getFoo());
8789
assertEquals("child 2", obj.getMoreChildren().get(1).getFoo());
90+
assertEquals(TestThing.class, obj.getTestThing().getClass());
91+
assertEquals(123, obj.getTestThing().getId().intValue());
92+
}
93+
94+
@Test
95+
public void testReadPropertyWithIncorrectComplexTypeCoercesTheType() throws Exception {
96+
Entity entity = fromJson(Entity.class,
97+
"{"
98+
+ "\"complexType\": \"SoftLayer_TestEntity\","
99+
+ "\"testThing\": {\"complexType\": \"SoftLayer_TestEntity\", \"id\": 123, \"foo\": \"unknown!\"}"
100+
+ "}");
101+
assertEquals(TestEntity.class, entity.getClass());
102+
TestEntity obj = (TestEntity) entity;
103+
assertEquals(0, obj.getUnknownProperties().size());
104+
assertEquals(TestThing.class, obj.getTestThing().getClass());
105+
assertEquals(123, obj.getTestThing().getId().intValue());
106+
assertEquals(1, obj.getTestThing().getUnknownProperties().size());
107+
assertEquals("unknown!", obj.getTestThing().getUnknownProperties().get("foo"));
108+
}
109+
110+
111+
@Test
112+
public void testReadPropertyWithUnknownComplexTypeCoercesTheType() throws Exception {
113+
Entity entity = fromJson(Entity.class,
114+
"{"
115+
+ "\"complexType\": \"SoftLayer_TestEntity\","
116+
+ "\"testThing\": {\"complexType\": \"WhoKnows\", \"id\": 123, \"foo\": \"unknown!\"}"
117+
+ "}");
118+
assertEquals(TestEntity.class, entity.getClass());
119+
TestEntity obj = (TestEntity) entity;
120+
assertEquals(0, obj.getUnknownProperties().size());
121+
assertEquals(TestThing.class, obj.getTestThing().getClass());
122+
assertEquals(123, obj.getTestThing().getId().intValue());
123+
assertEquals(1, obj.getTestThing().getUnknownProperties().size());
124+
assertEquals("unknown!", obj.getTestThing().getUnknownProperties().get("foo"));
125+
}
126+
127+
@Test
128+
public void testReadPropertyThrowsExceptionWithoutComplexType() {
129+
130+
Exception e = assertThrows(RuntimeException.class, () -> {
131+
Entity entity = fromJson(Entity.class,
132+
"{"
133+
+ "\"complexType\": \"SoftLayer_TestEntity\","
134+
+ "\"testThing\": {\"id\": 123}"
135+
+ "}");
136+
});
137+
138+
assertEquals("Expected 'complexType' as first property", e.getMessage());
139+
}
140+
141+
@Test
142+
public void testReadPropertyThrowsExceptioWithComplexTypeNotFirst() {
143+
144+
Exception e = assertThrows(RuntimeException.class, () -> {
145+
Entity entity = fromJson(Entity.class,
146+
"{"
147+
+ "\"testThing\": {\"id\": 123},"
148+
+ "\"complexType\": \"SoftLayer_TestEntity\""
149+
+ "}");
150+
});
151+
152+
assertEquals("Expected 'complexType' as first property", e.getMessage());
88153
}
89154

90155
@Test

src/test/java/com/softlayer/api/service/TestEntity.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import com.softlayer.api.annotation.ApiType;
1212
import com.softlayer.api.ApiClient;
1313
import com.softlayer.api.ResponseHandler;
14-
import com.softlayer.api.ResultLimit;
1514

1615
@ApiType("SoftLayer_TestEntity")
1716
public class TestEntity extends Entity {

src/test/java/com/softlayer/api/service/TestThing.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
import com.softlayer.api.annotation.ApiMethod;
1111
import com.softlayer.api.annotation.ApiProperty;
1212
import com.softlayer.api.annotation.ApiService;
13+
import com.softlayer.api.annotation.ApiType;
1314

15+
@ApiType("SoftLayer_TestThing")
1416
public class TestThing extends Entity {
1517

1618
@ApiProperty(canBeNullOrNotSet = true)

0 commit comments

Comments
 (0)