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

smartsheet / smartsheet-java-sdk / #67

09 Jul 2026 02:57PM UTC coverage: 59.797% (-0.2%) from 59.997%
#67

push

github

web-flow
Merge pull request #183 from smartsheet/release/4.2.0

Prepare for release v4.2.0

4532 of 7579 relevant lines covered (59.8%)

0.6 hits per line

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

74.47
/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.ReportFilterObjectValue;
43
import com.smartsheet.api.models.Result;
44
import com.smartsheet.api.models.TokenPaginatedResult;
45
import com.smartsheet.api.models.ListAssetSharesResponse;
46
import com.smartsheet.api.models.WidgetContent;
47
import com.smartsheet.api.models.format.Format;
48

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

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

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

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

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

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

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

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

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

102
        module = new SimpleModule("ReportFilterObjectValueDeserializerModule", Version.unknownVersion());
1✔
103
        module.addDeserializer(ReportFilterObjectValue.class, new ReportFilterObjectValueDeserializer());
1✔
104
        OBJECT_MAPPER.registerModule(module);
1✔
105

106
        module = new SimpleModule("PrimitiveObjectValueSerializerModule", Version.unknownVersion());
1✔
107
        module.addSerializer(PrimitiveObjectValue.class, new PrimitiveObjectValueSerializer());
1✔
108
        OBJECT_MAPPER.registerModule(module);
1✔
109

110
        module = new SimpleModule("RecipientDeserializerModule", Version.unknownVersion());
1✔
111
        module.addDeserializer(Recipient.class, new RecipientDeserializer());
1✔
112
        OBJECT_MAPPER.registerModule(module);
1✔
113

114
        module = new SimpleModule("WidgetContentDeserializerModule", Version.unknownVersion());
1✔
115
        module.addDeserializer(WidgetContent.class, new WidgetContentDeserializer());
1✔
116
        OBJECT_MAPPER.registerModule(module);
1✔
117

118
        module = new SimpleModule("HyperlinkSerializerModule", Version.unknownVersion());
1✔
119
        module.addSerializer(Hyperlink.class, new HyperlinkSerializer());
1✔
120
        OBJECT_MAPPER.registerModule(module);
1✔
121

122
        module = new SimpleModule("CellSerializerModule", Version.unknownVersion());
1✔
123
        module.setSerializerModifier(new CellSerializerModifier());
1✔
124
        OBJECT_MAPPER.registerModule(module);
1✔
125

126
        module = new SimpleModule("ErrorDetailDeserializerModule", Version.unknownVersion());
1✔
127
        module.addDeserializer(com.smartsheet.api.models.Error.class, new ErrorDeserializer());
1✔
128
        OBJECT_MAPPER.registerModule(module);
1✔
129

130
        OBJECT_MAPPER.registerModule(new JavaTimeModule());
1✔
131
    }
1✔
132

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

142
    /**
143
     * Constructor.
144
     * <p>
145
     * Parameters: None
146
     * <p>
147
     * Exceptions: None
148
     */
149
    public JacksonJsonSerializer() {
1✔
150
    }
1✔
151

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

172
        try {
173
            OBJECT_MAPPER.writeValue(outputStream, object);
1✔
174
        } catch (JsonGenerationException e) {
×
175
            throw new JSONSerializerException(e);
×
176
        } catch (JsonMappingException e) {
1✔
177
            throw new JSONSerializerException(e);
1✔
178
        } catch (IOException e) {
1✔
179
            throw new JSONSerializerException(e);
1✔
180
        }
1✔
181
    }
1✔
182

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

203
        try {
204
            value = OBJECT_MAPPER.writeValueAsString(object);
1✔
205
        } catch (JsonGenerationException e) {
×
206
            throw new JSONSerializerException(e);
×
207
        } catch (JsonMappingException e) {
×
208
            throw new JSONSerializerException(e);
×
209
        } catch (IOException e) {
×
210
            throw new JSONSerializerException(e);
×
211
        }
1✔
212
        return value;
1✔
213
    }
214

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

231
        return OBJECT_MAPPER.readValue(inputStream, objectClass);
1✔
232
    }
233

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

251
        List<T> list = null;
1✔
252

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

265
        return list;
1✔
266
    }
267

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

278
        PagedResult<T> rw = null;
1✔
279

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

296
        return rw;
1✔
297
    }
298

299
    /**
300
     * De-serialize to a map from JSON.
301
     */
302
    // @Override
303
    public Map<String, Object> deserializeMap(InputStream inputStream) throws JSONSerializerException {
304
        Util.throwIfNull(inputStream);
1✔
305

306
        Map<String, Object> map = null;
1✔
307

308
        try {
309
            map = OBJECT_MAPPER.readValue(inputStream, new TypeReference<Map<String, Object>>() {
1✔
310
            });
311
        } catch (JsonParseException e) {
1✔
312
            throw new JSONSerializerException(e);
1✔
313
        } catch (JsonMappingException e) {
1✔
314
            throw new JSONSerializerException(e);
1✔
315
        } catch (IOException e) {
1✔
316
            throw new JSONSerializerException(e);
1✔
317
        }
1✔
318

319
        return map;
1✔
320
    }
321

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

338
        Result<T> result = null;
1✔
339

340
        try {
341
            result = OBJECT_MAPPER.readValue(inputStream,
1✔
342
                    OBJECT_MAPPER.getTypeFactory().constructParametrizedType(Result.class, Result.class, objectClass));
1✔
343
        } catch (JsonParseException e) {
1✔
344
            throw new JSONSerializerException(e);
1✔
345
        } catch (JsonMappingException e) {
1✔
346
            throw new JSONSerializerException(e);
1✔
347
        } catch (IOException e) {
1✔
348
            throw new JSONSerializerException(e);
1✔
349
        }
1✔
350

351
        return result;
1✔
352
    }
353

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

373
        Result<List<T>> result = null;
1✔
374

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

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

407
    /**
408
     * De-serialize to a CopyOrMoveRowResult object from JSON
409
     */
410
    @Override
411
    public CopyOrMoveRowResult deserializeCopyOrMoveRow(
412
            java.io.InputStream inputStream
413
    ) throws JSONSerializerException {
414
        Util.throwIfNull(inputStream);
1✔
415

416
        CopyOrMoveRowResult rw = null;
1✔
417

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

429
        return rw;
1✔
430
    }
431

432
    /**
433
     * De-serialize to a EventResult (holds pagination info) from JSON
434
     */
435
    @Override
436
    public EventResult deserializeEventResult(java.io.InputStream inputStream) throws JSONSerializerException {
437
        Util.throwIfNull(inputStream);
1✔
438

439
        EventResult rw = null;
1✔
440

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

452
        return rw;
1✔
453
    }
454

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

469
        TokenPaginatedResult<T> result = null;
×
470

471
        try {
472
            // Create a temporary ObjectMapper with the custom deserializer
473
            ObjectMapper tempMapper = OBJECT_MAPPER.copy();
×
474
            SimpleModule module = new SimpleModule("TokenPaginatedResultDeserializerModule", Version.unknownVersion());
×
475
            module.addDeserializer(List.class, deserializer);
×
476
            tempMapper.registerModule(module);
×
477

478
            // Deserialize using the temporary mapper with custom deserializer
479
            result = tempMapper.readValue(inputStream,
×
480
                    tempMapper.getTypeFactory().constructParametricType(TokenPaginatedResult.class, Object.class));
×
481
        } catch (IOException e) {
×
482
            throw new JSONSerializerException(e);
×
483
        }
×
484

485
        return result;
×
486
    }
487

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

502
        TokenPaginatedResult<T> result = null;
1✔
503
        try {
504
            result = OBJECT_MAPPER.readValue(inputStream,
1✔
505
                    OBJECT_MAPPER.getTypeFactory().constructParametricType(TokenPaginatedResult.class, objectClass));
1✔
506
        } catch (IOException e) {
×
507
            throw new JSONSerializerException(e);
×
508
        }
1✔
509

510
        return result;
1✔
511
    }
512

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

527
        ListAssetSharesResponse<T> result = null;
1✔
528
        try {
529
            result = OBJECT_MAPPER.readValue(inputStream,
1✔
530
                    OBJECT_MAPPER.getTypeFactory().constructParametricType(ListAssetSharesResponse.class, objectClass));
1✔
531
        } catch (IOException e) {
×
532
            throw new JSONSerializerException(e);
×
533
        }
1✔
534

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

© 2026 Coveralls, Inc