Skip to content

Commit ba8e67a

Browse files
authored
v4.5.1: Serializing null values correctly & bumping gson@2.8.9 (#32)
## v.4.5.1: Serializing null values correctly & bumping gson@2.8.9 ### Serializing `null` values in attribute objects correctly When sending integration requests, allowing to remove a value for a key by serializing nulls properly. As explained at the bottom of [this section](https://docs.talon.one/docs/dev/concepts/attributes/#setting-attributes) in our [documentation website](https://docs.talon.one/) closes #31 ### Upgrade gson dependency Bump the gson dependency a few minor versions up, to 2.8.9 to resolve a potential security issue with the current version (2.8.6) closes #30 ## Commit Summary * Add custom JsonNullable adapter and attribute class * Apply the JsonNullable attribute where applicable * Bump gson to 2.8.9 * Bump version to 4.5.1
1 parent 306b6a1 commit ba8e67a

16 files changed

+301
-46
lines changed

README.md

Lines changed: 117 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ Add this dependency to your project's POM:
5353
<dependency>
5454
<groupId>one.talon</groupId>
5555
<artifactId>talon-one-client</artifactId>
56-
<version>4.5.0</version>
56+
<version>4.5.1</version>
5757
<scope>compile</scope>
5858
</dependency>
5959
```
@@ -63,7 +63,7 @@ Add this dependency to your project's POM:
6363
Add this dependency to your project's build file:
6464

6565
```groovy
66-
compile "one.talon:talon-one-client:4.5.0"
66+
compile "one.talon:talon-one-client:4.5.1"
6767
```
6868

6969
### Others
@@ -76,49 +76,134 @@ mvn clean package
7676

7777
Then manually install the following JARs:
7878

79-
* `target/talon-one-client-4.5.0.jar`
79+
* `target/talon-one-client-4.5.1.jar`
8080
* `target/lib/*.jar`
8181

8282
## Getting Started
8383

8484
Please follow the [installation](#installation) instruction and execute the following Java code:
8585

86+
### Integration API
87+
88+
**Note:** The Integration API's V1 `Update customer session` and `Update customer profile` endpoints are now deprecated. Use their V2 instead. See [Migrating to V2](https://docs.talon.one/docs/dev/tutorials/migrating-to-v2) for more information.
89+
8690
```java
91+
package com.example.consumer;
92+
93+
import com.google.gson.Gson;
8794

88-
// Import classes:
8995
import one.talon.ApiClient;
90-
import one.talon.ApiException;
91-
import one.talon.Configuration;
92-
import one.talon.auth.*;
93-
import one.talon.models.*;
9496
import one.talon.api.IntegrationApi;
95-
96-
public class Example {
97-
public static void main(String[] args) {
98-
ApiClient defaultClient = Configuration.getDefaultApiClient();
99-
defaultClient.setBasePath("http://localhost");
100-
101-
// Configure API key authorization: api_key_v1
102-
ApiKeyAuth api_key_v1 = (ApiKeyAuth) defaultClient.getAuthentication("api_key_v1");
103-
api_key_v1.setApiKey("YOUR API KEY");
104-
// Uncomment the following line to set a prefix for the API key, e.g. "Token" (defaults to null)
105-
//api_key_v1.setApiKeyPrefix("Token");
106-
107-
IntegrationApi apiInstance = new IntegrationApi(defaultClient);
108-
NewAudience body = new NewAudience(); // NewAudience |
109-
try {
110-
Audience result = apiInstance.createAudienceV2(body);
111-
System.out.println(result);
112-
} catch (ApiException e) {
113-
System.err.println("Exception when calling IntegrationApi#createAudienceV2");
114-
System.err.println("Status code: " + e.getCode());
115-
System.err.println("Reason: " + e.getResponseBody());
116-
System.err.println("Response headers: " + e.getResponseHeaders());
117-
e.printStackTrace();
97+
import one.talon.api.ManagementApi;
98+
import one.talon.model.*;
99+
100+
public class TalonApiTest {
101+
public static void main(String[] args) {
102+
Gson gson = new Gson();
103+
IntegrationApi iApi = new IntegrationApi(new ApiClient("api_key_v1"));
104+
105+
// Setup: basePath
106+
iApi.getApiClient().setBasePath("https://mycompany.talon.one");
107+
// Setup: when using 'api_key_v1', set apiKey & apiKeyPrefix must be provided
108+
iApi.getApiClient().setApiKeyPrefix("ApiKey-v1");
109+
iApi.getApiClient().setApiKey("dbc644d33aa74d582bd9479c59e16f970fe13bf34a208c39d6c7fa7586968468");
110+
111+
try {
112+
// Creating a cart item object
113+
CartItem cartItem = new CartItem();
114+
cartItem.setName("Hawaiian Pizza");
115+
cartItem.setSku("pizza-x");
116+
cartItem.setQuantity(1);
117+
cartItem.setPrice(new java.math.BigDecimal("5.5"));
118+
119+
// Creating a customer session of V2
120+
NewCustomerSessionV2 customerSession = new NewCustomerSessionV2();
121+
customerSession.setProfileId("Cool_Dude");
122+
customerSession.addCouponCodesItem("Cool-Summer!");
123+
customerSession.addCartItemsItem(cartItem);
124+
125+
// Initiating integration request wrapping the customer session update
126+
IntegrationRequest request = new IntegrationRequest()
127+
.customerSession(customerSession)
128+
// Optional parameter of requested information to be present on the response related to the customer session update
129+
.responseContent(Arrays.asList(
130+
IntegrationRequest.ResponseContentEnum.CUSTOMERSESSION,
131+
IntegrationRequest.ResponseContentEnum.CUSTOMERPROFILE
132+
));
133+
134+
// Flag to communicate whether the request is a "dry run"
135+
Boolean dryRun = false;
136+
137+
// Create/update a customer session using `updateCustomerSessionV2` function
138+
IntegrationStateV2 is = iApi.updateCustomerSessionV2("deetdoot", request, dryRun);
139+
System.out.println(is.toString());
140+
141+
// Parsing the returned effects list, please consult https://developers.talon.one/Integration-API/handling-effects-v2 for the full list of effects and their corresponding properties
142+
for (Effect eff : is.getEffects()) {
143+
if (eff.getEffectType().equals("addLoyaltyPoints")) {
144+
// Typecasting according to the specific effect type
145+
AddLoyaltyPointsEffectProps props = gson.fromJson(
146+
gson.toJson(eff.getProps()),
147+
AddLoyaltyPointsEffectProps.class
148+
);
149+
// Access the specific effect's properties
150+
System.out.println(props.getName());
151+
System.out.println(props.getProgramId());
152+
System.out.println(props.getValue());
153+
}
154+
if (eff.getEffectType().equals("acceptCoupon")) {
155+
// Typecasting according to the specific effect type
156+
AcceptCouponEffectProps props = gson.fromJson(
157+
gson.toJson(eff.getProps()),
158+
AcceptCouponEffectProps.class
159+
);
160+
// work with AcceptCouponEffectProps' properties
161+
// ...
162+
}
163+
}
164+
} catch (Exception e) {
165+
System.out.println(e);
166+
}
118167
}
119-
}
120168
}
169+
```
170+
171+
### Management API
121172

173+
```java
174+
package com.example.consumer;
175+
176+
import one.talon.ApiClient;
177+
import one.talon.api.IntegrationApi;
178+
import one.talon.api.ManagementApi;
179+
import one.talon.model.*;
180+
181+
public class TalonApiTest {
182+
public static void main(String[] args) {
183+
// Management API example to load application with id 7
184+
ManagementApi mApi = new ManagementApi(new ApiClient("manager_auth"));
185+
186+
// Setup: basePath and bearer prefix
187+
mApi.getApiClient().setBasePath("https://mycompany.talon.one");
188+
mApi.getApiClient().setApiKeyPrefix("Bearer");
189+
190+
LoginParams lp = new LoginParams();
191+
lp.setEmail("admin@talon.one");
192+
lp.setPassword("yourpassword");
193+
194+
try {
195+
// Acquire session token
196+
Session s = mApi.createSession(lp);
197+
mApi.getApiClient().setApiKey(s.getToken());
198+
199+
// Calling `getApplication` function with the desired id (7)
200+
Application application = mApi.getApplication(7);
201+
System.out.println(application.toString());
202+
} catch (Exception e) {
203+
System.out.println(e);
204+
}
205+
}
206+
}
122207
```
123208

124209
## Documentation for API Endpoints

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ apply plugin: 'eclipse'
33
apply plugin: 'java'
44

55
group = 'one.talon'
6-
version = '4.5.0'
6+
version = '4.5.1'
77

88
buildscript {
99
repositories {

build.sbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ lazy val root = (project in file(".")).
22
settings(
33
organization := "one.talon",
44
name := "talon-one-client",
5-
version := "4.5.0",
5+
version := "4.5.1",
66
scalaVersion := "2.11.4",
77
scalacOptions ++= Seq("-feature"),
88
javacOptions in compile ++= Seq("-Xlint:deprecation"),

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<artifactId>talon-one-client</artifactId>
66
<packaging>jar</packaging>
77
<name>talon-one-client</name>
8-
<version>4.5.0</version>
8+
<version>4.5.1</version>
99
<url>https://github.com/talon-one/maven-artefacts</url>
1010
<description>Talon.One unified JAVA SDK. It allows for programmatic access to the integration and management API with their respective authentication strategies</description>
1111
<scm>
@@ -271,7 +271,7 @@
271271
<gson-fire-version>1.8.4</gson-fire-version>
272272
<swagger-core-version>1.5.24</swagger-core-version>
273273
<okhttp-version>3.14.7</okhttp-version>
274-
<gson-version>2.8.6</gson-version>
274+
<gson-version>2.8.9</gson-version>
275275
<commons-lang3-version>3.10</commons-lang3-version>
276276
<threetenbp-version>1.4.3</threetenbp-version>
277277
<javax-annotation-version>1.3.2</javax-annotation-version>

src/main/java/one/talon/ApiClient.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ private void init() {
127127
json = new JSON();
128128

129129
// Set default User-Agent.
130-
setUserAgent("OpenAPI-Generator/4.5.0/java");
130+
setUserAgent("OpenAPI-Generator/4.5.1/java");
131131

132132
authentications = new HashMap<String, Authentication>();
133133
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package one.talon.custom;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Retention(RetentionPolicy.RUNTIME)
9+
@Target(ElementType.FIELD)
10+
public @interface JsonNullable {
11+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package one.talon.custom;
2+
3+
import com.google.gson.*;
4+
import com.google.gson.annotations.SerializedName;
5+
import com.google.gson.reflect.TypeToken;
6+
import com.google.gson.stream.JsonReader;
7+
import com.google.gson.stream.JsonWriter;
8+
9+
import javax.annotation.Nullable;
10+
import java.io.IOException;
11+
import java.lang.reflect.Field;
12+
import java.util.ArrayList;
13+
import java.util.Iterator;
14+
import java.util.List;
15+
16+
public class NullableAdapterFactory implements TypeAdapterFactory {
17+
18+
@Override
19+
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
20+
Field[] declaredFields = type.getRawType().getDeclaredFields();
21+
final List<String> nullableFieldNames = new ArrayList<>();
22+
final List<String> nonNullableFieldNames = new ArrayList<>();
23+
for (Field declaredField : declaredFields) {
24+
if (declaredField.isAnnotationPresent(JsonNullable.class)) {
25+
if (declaredField.getAnnotation(SerializedName.class) != null) {
26+
nullableFieldNames.add(declaredField.getAnnotation(SerializedName.class).value());
27+
} else {
28+
nullableFieldNames.add(declaredField.getName());
29+
}
30+
} else {
31+
if (declaredField.getAnnotation(SerializedName.class) != null) {
32+
nonNullableFieldNames.add(declaredField.getAnnotation(SerializedName.class).value());
33+
} else {
34+
nonNullableFieldNames.add(declaredField.getName());
35+
}
36+
}
37+
}
38+
39+
if (nullableFieldNames.size() == 0) {
40+
return null;
41+
}
42+
final TypeAdapter<T> delegateAdapter = gson.getDelegateAdapter(NullableAdapterFactory.this, type);
43+
final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
44+
45+
return new TypeAdapter<T>() {
46+
@Override
47+
public void write(JsonWriter out, T value) throws IOException {
48+
JsonObject jsonObject = delegateAdapter.toJsonTree(value).getAsJsonObject();
49+
for (String name: nonNullableFieldNames) {
50+
if (jsonObject.has(name)) {
51+
JsonElement element =jsonObject.get(name);
52+
if(element==null || element instanceof JsonNull){
53+
jsonObject.remove(name);
54+
}
55+
if(element==null || element instanceof JsonObject){
56+
removeNullsFrom((JsonObject) element);
57+
}
58+
if(element instanceof JsonArray){
59+
removeNullsFrom((JsonArray) element);
60+
}
61+
}
62+
}
63+
64+
// remove null-valued nullablae fields; preventing them getting output if they do not have _any value_
65+
for (String name: nullableFieldNames) {
66+
if (jsonObject.has(name)) {
67+
JsonElement element =jsonObject.get(name);
68+
if(element==null || element instanceof JsonNull){
69+
jsonObject.remove(name);
70+
}
71+
}
72+
}
73+
74+
boolean originalSerializeNulls = out.getSerializeNulls();
75+
out.setSerializeNulls(true);
76+
elementAdapter.write(out, jsonObject);
77+
out.setSerializeNulls(originalSerializeNulls);
78+
}
79+
80+
@Override
81+
public T read(JsonReader in) throws IOException {
82+
return delegateAdapter.read(in);
83+
}
84+
85+
public void removeNullsFrom(@Nullable JsonObject object) {
86+
if (object != null) {
87+
Iterator<String> iterator = object.keySet().iterator();
88+
while (iterator.hasNext()) {
89+
String key = iterator.next();
90+
Object o = object.get(key);
91+
if (o == null || o instanceof JsonNull) {
92+
iterator.remove();
93+
}
94+
else if(o instanceof JsonObject){
95+
removeNullsFrom((JsonObject) o);
96+
}
97+
else if(o instanceof JsonArray){
98+
removeNullsFrom((JsonArray) o);
99+
}
100+
}
101+
}
102+
}
103+
104+
public void removeNullsFrom(@Nullable JsonArray array) {
105+
if (array != null) {
106+
for (int i = 0; i < array.size(); i++) {
107+
Object o = array.get(i);
108+
if (o == null || o instanceof JsonNull) {
109+
array.remove(i);
110+
} else if(o instanceof JsonObject){
111+
removeNullsFrom((JsonObject) o);
112+
}
113+
else if(o instanceof JsonArray){
114+
removeNullsFrom((JsonArray) o);
115+
}
116+
}
117+
}
118+
}
119+
120+
121+
};
122+
}
123+
}

src/main/java/one/talon/model/CartItem.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@
2828
import java.util.List;
2929
import java.util.Map;
3030
import one.talon.model.AdditionalCost;
31+
import one.talon.custom.JsonNullable;
32+
import one.talon.custom.NullableAdapterFactory;
3133

3234
/**
3335
* CartItem
3436
*/
35-
37+
@JsonAdapter(NullableAdapterFactory.class)
3638
public class CartItem {
3739
public static final String SERIALIZED_NAME_NAME = "name";
3840
@SerializedName(SERIALIZED_NAME_NAME)
@@ -84,6 +86,8 @@ public class CartItem {
8486

8587
public static final String SERIALIZED_NAME_ATTRIBUTES = "attributes";
8688
@SerializedName(SERIALIZED_NAME_ATTRIBUTES)
89+
/*allow Serializing null for this field */
90+
@JsonNullable
8791
private Object attributes;
8892

8993
public static final String SERIALIZED_NAME_ADDITIONAL_COSTS = "additionalCosts";

0 commit comments

Comments
 (0)