• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

smartsheet / smartsheet-java-sdk / #60

24 Oct 2025 09:43AM UTC coverage: 60.156% (+0.4%) from 59.737%
#60

push

github

web-flow
Add Wiremock integration tests and update dependencies (#145)

* Add Wiremock integration tests and update dependencies

* Update test method names

* Bump version to 3.9.0 and address comments for the wiremock integration tests

* Refactor Wiremock integration tests for user resources to improve query parameter handling and update endpoint paths

* Fix imports

* Fix checkstyle errors

* Move wiremock tests to sdktest

* Remove redundant Wiremock tests from UserResourcesIT

* Remove unused imports from UserResourcesIT

* Revert UserResourcesIT

* Fix changelog

* Add copyright to WiremockTest

* Update wiremock base uri port

* Rename WiremockTest to UserResourcesContractTests for clarity

* Change WireMock default port

4392 of 7301 relevant lines covered (60.16%)

0.6 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.05
/src/main/java/com/smartsheet/api/internal/json/JacksonJsonSerializer.java
1
/*
2
 * Copyright (C) 2025 Smartsheet
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package com.smartsheet.api.internal.json;
18

19
import com.fasterxml.jackson.annotation.JsonInclude.Include;
20
import com.fasterxml.jackson.core.JsonGenerationException;
21
import com.fasterxml.jackson.core.JsonParseException;
22
import com.fasterxml.jackson.core.Version;
23
import com.fasterxml.jackson.core.type.TypeReference;
24
import com.fasterxml.jackson.databind.DeserializationFeature;
25
import com.fasterxml.jackson.databind.JsonDeserializer;
26
import com.fasterxml.jackson.databind.JsonMappingException;
27
import com.fasterxml.jackson.databind.ObjectMapper;
28
import com.fasterxml.jackson.databind.SerializationFeature;
29
import com.fasterxml.jackson.databind.module.SimpleModule;
30
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
31
import com.smartsheet.api.internal.util.Util;
32
import com.smartsheet.api.models.BulkItemResult;
33
import com.smartsheet.api.models.CopyOrMoveRowResult;
34
import com.smartsheet.api.models.EventResult;
35
import com.smartsheet.api.models.Hyperlink;
36
import com.smartsheet.api.models.IdentifiableModel;
37
import com.smartsheet.api.models.IdentifiableModelMixin;
38
import com.smartsheet.api.models.ObjectValue;
39
import com.smartsheet.api.models.PagedResult;
40
import com.smartsheet.api.models.PrimitiveObjectValue;
41
import com.smartsheet.api.models.Recipient;
42
import com.smartsheet.api.models.Result;
43
import com.smartsheet.api.models.TokenPaginatedResult;
44
import com.smartsheet.api.models.ListAssetSharesResponse;
45
import com.smartsheet.api.models.WidgetContent;
46
import com.smartsheet.api.models.format.Format;
47

48
import java.io.IOException;
49
import java.io.InputStream;
50
import java.text.SimpleDateFormat;
51
import java.util.List;
52
import java.util.Map;
53
import java.util.TimeZone;
54

55
/**
56
 * This is the Jackson based JsonSerializer implementation.
57
 * <p>
58
 * Thread Safety: This class is thread safe because it is immutable and the underlying Jackson ObjectMapper is thread
59
 * safe as long as it is not re-configured.
60
 */
61
public class JacksonJsonSerializer implements JsonSerializer {
62
    /**
63
     * Represents the ObjectMapper used to serialize/de-serialize JSON.
64
     * <p>
65
     * It will be initialized in a static initializer and will not change afterwards.
66
     * <p>
67
     * Because ObjectMapper is thread-safe as long as it's not reconfigured, a static final class-level ObjectMapper is
68
     * used to achieve best performance.
69
     */
70
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
1✔
71

72
    static {
73
        // Allow deserialization if there are properties that can't be deserialized
74
        OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
1✔
75
        OBJECT_MAPPER.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true);
1✔
76

77
        // Only include non-null properties in when serializing java beans
78
        OBJECT_MAPPER.setSerializationInclusion(Include.NON_NULL);
1✔
79

80
        // Use toString() method on enums to serialize and deserialize
81
        OBJECT_MAPPER.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
1✔
82
        OBJECT_MAPPER.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
1✔
83

84
        OBJECT_MAPPER.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
1✔
85
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
1✔
86
        df.setTimeZone(TimeZone.getTimeZone("UTC"));
1✔
87
        OBJECT_MAPPER.setDateFormat(df);
1✔
88

89
        // Add a custom deserializer that will convert a string to a Format object.
90
        SimpleModule module = new SimpleModule("FormatDeserializerModule", Version.unknownVersion());
1✔
91
        module.addDeserializer(Format.class, new FormatDeserializer());
1✔
92

93
        // Add custom mixin to ignore getId() for the IdentifiableModel class
94
        module.setMixInAnnotation(IdentifiableModel.class, IdentifiableModelMixin.class);
1✔
95
        OBJECT_MAPPER.registerModule(module);
1✔
96

97
        module = new SimpleModule("ObjectValueDeserializerModule", Version.unknownVersion());
1✔
98
        module.addDeserializer(ObjectValue.class, new ObjectValueDeserializer());
1✔
99
        OBJECT_MAPPER.registerModule(module);
1✔
100

101
        module = new SimpleModule("PrimitiveObjectValueSerializerModule", Version.unknownVersion());
1✔
102
        module.addSerializer(PrimitiveObjectValue.class, new PrimitiveObjectValueSerializer());
1✔
103
        OBJECT_MAPPER.registerModule(module);
1✔
104

105
        module = new SimpleModule("RecipientDeserializerModule", Version.unknownVersion());
1✔
106
        module.addDeserializer(Recipient.class, new RecipientDeserializer());
1✔
107
        OBJECT_MAPPER.registerModule(module);
1✔
108

109
        module = new SimpleModule("WidgetContentDeserializerModule", Version.unknownVersion());
1✔
110
        module.addDeserializer(WidgetContent.class, new WidgetContentDeserializer());
1✔
111
        OBJECT_MAPPER.registerModule(module);
1✔
112

113
        module = new SimpleModule("HyperlinkSerializerModule", Version.unknownVersion());
1✔
114
        module.addSerializer(Hyperlink.class, new HyperlinkSerializer());
1✔
115
        OBJECT_MAPPER.registerModule(module);
1✔
116

117
        module = new SimpleModule("CellSerializerModule", Version.unknownVersion());
1✔
118
        module.setSerializerModifier(new CellSerializerModifier());
1✔
119
        OBJECT_MAPPER.registerModule(module);
1✔
120

121
        module = new SimpleModule("ErrorDetailDeserializerModule", Version.unknownVersion());
1✔
122
        module.addDeserializer(com.smartsheet.api.models.Error.class, new ErrorDeserializer());
1✔
123
        OBJECT_MAPPER.registerModule(module);
1✔
124

125
        OBJECT_MAPPER.registerModule(new JavaTimeModule());
1✔
126
    }
1✔
127

128
    /**
129
     * Sets if the OBJECT MAPPER should ignore unknown properties or fail when de-serializing the JSON data.
130
     *
131
     * @param value true if it should fail, false otherwise.
132
     */
133
    public static void setFailOnUnknownProperties(boolean value) {
134
        OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, value);
1✔
135
    }
1✔
136

137
    /**
138
     * Constructor.
139
     * <p>
140
     * Parameters: None
141
     * <p>
142
     * Exceptions: None
143
     */
144
    public JacksonJsonSerializer() {
1✔
145
    }
1✔
146

147
    /**
148
     * Serialize an object to JSON.
149
     * <p>
150
     * Parameters:
151
     * object : the object to serialize
152
     * outputStream : the output stream to which the JSON will be written
153
     * <p>
154
     * Returns: None
155
     * <p>
156
     * Exceptions: - IllegalArgumentException : if any argument is null - JSONSerializerException : if there is any
157
     * other error occurred during the operation
158
     *
159
     * @param outputStream the output stream to write the deserialized object to
160
     * @param object       object to serialize
161
     * @throws JSONSerializerException thrown for any serialization exception we catch
162
     */
163
    // @Override
164
    public <T> void serialize(T object, java.io.OutputStream outputStream) throws JSONSerializerException {
165
        Util.throwIfNull(object, outputStream);
1✔
166

167
        try {
168
            OBJECT_MAPPER.writeValue(outputStream, object);
1✔
169
        } catch (JsonGenerationException e) {
×
170
            throw new JSONSerializerException(e);
×
171
        } catch (JsonMappingException e) {
1✔
172
            throw new JSONSerializerException(e);
1✔
173
        } catch (IOException e) {
1✔
174
            throw new JSONSerializerException(e);
1✔
175
        }
1✔
176
    }
1✔
177

178
    /**
179
     * Serialize an object to JSON.
180
     * <p>
181
     * Parameters:
182
     * object : the object to serialize
183
     * outputStream : the output stream to which the JSON will be written
184
     * <p>
185
     * Returns: None
186
     * <p>
187
     * Exceptions: - IllegalArgumentException : if any argument is null - JSONSerializerException : if there is any
188
     * other error occurred during the operation
189
     *
190
     * @param object the object to serialized
191
     * @return a string with the deserialized object
192
     * @throws JSONSerializerException thrown for any serialization exception we catch
193
     */
194
    public <T> String serialize(T object) throws JSONSerializerException {
195
        Util.throwIfNull(object);
1✔
196
        String value;
197

198
        try {
199
            value = OBJECT_MAPPER.writeValueAsString(object);
1✔
200
        } catch (JsonGenerationException e) {
×
201
            throw new JSONSerializerException(e);
×
202
        } catch (JsonMappingException e) {
×
203
            throw new JSONSerializerException(e);
×
204
        } catch (IOException e) {
×
205
            throw new JSONSerializerException(e);
×
206
        }
1✔
207
        return value;
1✔
208
    }
209

210
    /**
211
     * De-serialize an object from JSON.
212
     * <p>
213
     * Returns: the de-serialized object
214
     * <p>
215
     * Exceptions:
216
     * - IllegalArgumentException : if any argument is null
217
     * - JSONSerializerException : if there is any other error occurred during the operation
218
     *
219
     * @param inputStream the input stream from which the JSON will be read
220
     * @param objectClass the class of the object to de-serialize
221
     */
222
    // @Override
223
    public <T> T deserialize(Class<T> objectClass, java.io.InputStream inputStream) throws IOException {
224
        Util.throwIfNull(objectClass, inputStream);
1✔
225

226
        return OBJECT_MAPPER.readValue(inputStream, objectClass);
1✔
227
    }
228

229
    /**
230
     * De-serialize an object list from JSON.
231
     * <p>
232
     * Returns: the de-serialized list
233
     * <p>
234
     * Exceptions:
235
     * - IllegalArgumentException : if any argument is null
236
     * - JSONSerializerException : if there is any other error occurred during the operation
237
     *
238
     * @param inputStream the input stream from which the JSON will be read
239
     * @param objectClass the class of the object (of the list) to de-serialize
240
     */
241
    // @Override
242
    public <T> List<T> deserializeList(Class<T> objectClass, java.io.InputStream inputStream)
243
            throws JSONSerializerException {
244
        Util.throwIfNull(objectClass, inputStream);
1✔
245

246
        List<T> list = null;
1✔
247

248
        try {
249
            // Read the json input stream into a List.
250
            list = OBJECT_MAPPER.readValue(inputStream,
1✔
251
                    OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, objectClass));
1✔
252
        } catch (JsonParseException e) {
1✔
253
            throw new JSONSerializerException(e);
1✔
254
        } catch (JsonMappingException e) {
1✔
255
            throw new JSONSerializerException(e);
1✔
256
        } catch (IOException e) {
1✔
257
            throw new JSONSerializerException(e);
1✔
258
        }
1✔
259

260
        return list;
1✔
261
    }
262

263
    /**
264
     * De-serialize to a PagedResult (holds pagination info) from JSON
265
     */
266
    @Override
267
    public <T> PagedResult<T> deserializeDataWrapper(
268
            Class<T> objectClass,
269
            java.io.InputStream inputStream
270
    ) throws JSONSerializerException {
271
        Util.throwIfNull(objectClass, inputStream);
1✔
272

273
        PagedResult<T> rw = null;
1✔
274

275
        try {
276
            // Read the json input stream into a List.
277
            rw = OBJECT_MAPPER.readValue(
1✔
278
                    inputStream,
279
                    OBJECT_MAPPER
280
                            .getTypeFactory()
1✔
281
                            .constructParametrizedType(PagedResult.class, PagedResult.class, objectClass)
1✔
282
            );
283
        } catch (JsonParseException e) {
×
284
            throw new JSONSerializerException(e);
×
285
        } catch (JsonMappingException e) {
×
286
            throw new JSONSerializerException(e);
×
287
        } catch (IOException e) {
×
288
            throw new JSONSerializerException(e);
×
289
        }
1✔
290

291
        return rw;
1✔
292
    }
293

294
    /**
295
     * De-serialize to a map from JSON.
296
     */
297
    // @Override
298
    public Map<String, Object> deserializeMap(InputStream inputStream) throws JSONSerializerException {
299
        Util.throwIfNull(inputStream);
1✔
300

301
        Map<String, Object> map = null;
1✔
302

303
        try {
304
            map = OBJECT_MAPPER.readValue(inputStream, new TypeReference<Map<String, Object>>() {
1✔
305
            });
306
        } catch (JsonParseException e) {
1✔
307
            throw new JSONSerializerException(e);
1✔
308
        } catch (JsonMappingException e) {
1✔
309
            throw new JSONSerializerException(e);
1✔
310
        } catch (IOException e) {
1✔
311
            throw new JSONSerializerException(e);
1✔
312
        }
1✔
313

314
        return map;
1✔
315
    }
316

317
    /**
318
     * De-serialize a Result object from JSON.
319
     * <p>
320
     * Exceptions:
321
     * - IllegalArgumentException : if any argument is null
322
     * - JSONSerializerException : if there is any other error occurred during the operation
323
     *
324
     * @param inputStream the input stream from which the JSON will be read
325
     * @param objectClass the class of the object (of the Result) to de-serialize
326
     * @return the de-serialized result
327
     */
328
    // @Override
329
    public <T> Result<T> deserializeResult(Class<T> objectClass, java.io.InputStream inputStream)
330
            throws JSONSerializerException {
331
        Util.throwIfNull(objectClass, inputStream);
1✔
332

333
        Result<T> result = null;
1✔
334

335
        try {
336
            result = OBJECT_MAPPER.readValue(inputStream,
1✔
337
                    OBJECT_MAPPER.getTypeFactory().constructParametrizedType(Result.class, Result.class, objectClass));
1✔
338
        } catch (JsonParseException e) {
1✔
339
            throw new JSONSerializerException(e);
1✔
340
        } catch (JsonMappingException e) {
1✔
341
            throw new JSONSerializerException(e);
1✔
342
        } catch (IOException e) {
1✔
343
            throw new JSONSerializerException(e);
1✔
344
        }
1✔
345

346
        return result;
1✔
347
    }
348

349
    /**
350
     * De-serialize a List Result object from JSON.
351
     * <p>
352
     * Parameters: - objectClass :  - inputStream :
353
     * <p>
354
     * Returns: the de-serialized result
355
     * <p>
356
     * Exceptions:
357
     * - IllegalArgumentException : if any argument is null
358
     * - JSONSerializerException : if there is any other error occurred during the operation
359
     *
360
     * @param inputStream the input stream from which the JSON will be read
361
     * @param objectClass the class of the object (of the Result) to de-serialize
362
     */
363
    // @Override
364
    public <T> Result<List<T>> deserializeListResult(Class<T> objectClass, java.io.InputStream inputStream)
365
            throws JSONSerializerException {
366
        Util.throwIfNull(objectClass, inputStream);
1✔
367

368
        Result<List<T>> result = null;
1✔
369

370
        try {
371
            result = OBJECT_MAPPER.readValue(
1✔
372
                    inputStream,
373
                    OBJECT_MAPPER.getTypeFactory().constructParametrizedType(Result.class, Result.class,
1✔
374
                            OBJECT_MAPPER.getTypeFactory().constructParametrizedType(List.class, List.class, objectClass)));
1✔
375
        } catch (JsonParseException e) {
1✔
376
            throw new JSONSerializerException(e);
1✔
377
        } catch (JsonMappingException e) {
1✔
378
            throw new JSONSerializerException(e);
1✔
379
        } catch (IOException e) {
1✔
380
            throw new JSONSerializerException(e);
1✔
381
        }
1✔
382
        return result;
1✔
383
    }
384

385
    @Override
386
    public <T> BulkItemResult<T> deserializeBulkItemResult(Class<T> objectClass, InputStream inputStream)
387
            throws JSONSerializerException {
388
        BulkItemResult<T> result = null;
1✔
389
        try {
390
            result = OBJECT_MAPPER.readValue(inputStream,
1✔
391
                    OBJECT_MAPPER.getTypeFactory().constructParametrizedType(BulkItemResult.class, BulkItemResult.class, objectClass));
1✔
392
        } catch (JsonParseException e) {
×
393
            throw new JSONSerializerException(e);
×
394
        } catch (JsonMappingException e) {
×
395
            throw new JSONSerializerException(e);
×
396
        } catch (IOException e) {
×
397
            throw new JSONSerializerException(e);
×
398
        }
1✔
399
        return result;
1✔
400
    }
401

402
    /**
403
     * De-serialize to a CopyOrMoveRowResult object from JSON
404
     */
405
    @Override
406
    public CopyOrMoveRowResult deserializeCopyOrMoveRow(
407
            java.io.InputStream inputStream
408
    ) throws JSONSerializerException {
409
        Util.throwIfNull(inputStream);
1✔
410

411
        CopyOrMoveRowResult rw = null;
1✔
412

413
        try {
414
            // Read the json input stream into a List.
415
            rw = OBJECT_MAPPER.readValue(inputStream, CopyOrMoveRowResult.class);
1✔
416
        } catch (JsonParseException e) {
×
417
            throw new JSONSerializerException(e);
×
418
        } catch (JsonMappingException e) {
×
419
            throw new JSONSerializerException(e);
×
420
        } catch (IOException e) {
×
421
            throw new JSONSerializerException(e);
×
422
        }
1✔
423

424
        return rw;
1✔
425
    }
426

427
    /**
428
     * De-serialize to a EventResult (holds pagination info) from JSON
429
     */
430
    @Override
431
    public EventResult deserializeEventResult(java.io.InputStream inputStream) throws JSONSerializerException {
432
        Util.throwIfNull(inputStream);
1✔
433

434
        EventResult rw = null;
1✔
435

436
        try {
437
            // Read the json input stream into a List.
438
            rw = OBJECT_MAPPER.readValue(inputStream, EventResult.class);
1✔
439
        } catch (JsonParseException e) {
×
440
            throw new JSONSerializerException(e);
×
441
        } catch (JsonMappingException e) {
×
442
            throw new JSONSerializerException(e);
×
443
        } catch (IOException e) {
×
444
            throw new JSONSerializerException(e);
×
445
        }
1✔
446

447
        return rw;
1✔
448
    }
449

450
    /**
451
     * De-serialize json to TokenPaginatedResult using a custom deserializer.
452
     *
453
     * @param <T> the generic type of the data items
454
     * @param deserializer the custom deserializer for the data items
455
     * @param inputStream the input stream
456
     * @return the TokenPaginatedResult containing a list of type T
457
     * @throws JSONSerializerException the JSON serializer exception
458
     */
459
    @Override
460
    public <T> TokenPaginatedResult<T> deserializeTokenPaginatedResult(JsonDeserializer<List<T>> deserializer, InputStream inputStream)
461
            throws JSONSerializerException {
462
        Util.throwIfNull(deserializer, inputStream);
×
463

464
        TokenPaginatedResult<T> result = null;
×
465

466
        try {
467
            // Create a temporary ObjectMapper with the custom deserializer
468
            ObjectMapper tempMapper = OBJECT_MAPPER.copy();
×
469
            SimpleModule module = new SimpleModule("TokenPaginatedResultDeserializerModule", Version.unknownVersion());
×
470
            module.addDeserializer(List.class, deserializer);
×
471
            tempMapper.registerModule(module);
×
472

473
            // Deserialize using the temporary mapper with custom deserializer
474
            result = tempMapper.readValue(inputStream,
×
475
                    tempMapper.getTypeFactory().constructParametricType(TokenPaginatedResult.class, Object.class));
×
476
        } catch (IOException e) {
×
477
            throw new JSONSerializerException(e);
×
478
        }
×
479

480
        return result;
×
481
    }
482

483
    /**
484
     * De-serialize json to TokenPaginatedResult using object class type
485
     *
486
     * @param <T> the generic type of the data items
487
     * @param objectClass actual data type wrapped in TokenPaginatedResult
488
     * @param inputStream the input stream
489
     * @return the TokenPaginatedResult containing a list of type T
490
     * @throws JSONSerializerException the JSON serializer exception
491
     */
492
    @Override
493
    public <T> TokenPaginatedResult<T> deserializeTokenPaginatedResult(Class<T> objectClass, InputStream inputStream)
494
            throws JSONSerializerException {
495
        Util.throwIfNull(inputStream);
1✔
496

497
        TokenPaginatedResult<T> result = null;
1✔
498
        try {
499
            result = OBJECT_MAPPER.readValue(inputStream,
1✔
500
                    OBJECT_MAPPER.getTypeFactory().constructParametricType(TokenPaginatedResult.class, objectClass));
1✔
501
        } catch (IOException e) {
×
502
            throw new JSONSerializerException(e);
×
503
        }
1✔
504

505
        return result;
1✔
506
    }
507

508
    /**
509
     * De-serialize json to ListAssetSharesResponse using object class type
510
     *
511
     * @param <T> the generic type of the data items
512
     * @param objectClass actual data type wrapped in ListAssetSharesResponse
513
     * @param inputStream the input stream
514
     * @return the ListAssetSharesResponse containing a list of type T
515
     * @throws JSONSerializerException the JSON serializer exception
516
     */
517
    @Override
518
    public <T> ListAssetSharesResponse<T> listAssetSharesTokenPaginatedResult(Class<T> objectClass, InputStream inputStream)
519
            throws JSONSerializerException {
520
        Util.throwIfNull(inputStream);
1✔
521

522
        ListAssetSharesResponse<T> result = null;
1✔
523
        try {
524
            result = OBJECT_MAPPER.readValue(inputStream,
1✔
525
                    OBJECT_MAPPER.getTypeFactory().constructParametricType(ListAssetSharesResponse.class, objectClass));
1✔
526
        } catch (IOException e) {
×
527
            throw new JSONSerializerException(e);
×
528
        }
1✔
529

530
        return result;
1✔
531
    }
532
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc