JacksonSerDes.java
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package software.amazon.lambda.durable.serde;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import software.amazon.lambda.durable.TypeToken;
import software.amazon.lambda.durable.exception.SerDesException;
/**
* Jackson-based implementation of {@link SerDes}.
*
* <p>This implementation uses Jackson's ObjectMapper for JSON serialization and deserialization, with support for both
* simple types via {@link Class} and complex generic types via {@link TypeToken}.
*
* <p>Features:
*
* <ul>
* <li>Java 8 time types support (Duration, Instant, LocalDateTime, etc.)
* <li>Dates serialized as ISO-8601 strings (not timestamps)
* <li>Unknown properties ignored during deserialization
* <li>Type cache for improved performance with generic types
* </ul>
*/
public class JacksonSerDes implements SerDes {
private final ObjectMapper mapper;
private final TypeFactory typeFactory;
private final Map<Type, JavaType> typeCache;
public JacksonSerDes() {
this.mapper = new ObjectMapper()
.registerModule(new JavaTimeModule())
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
this.typeFactory = mapper.getTypeFactory();
this.typeCache = new ConcurrentHashMap<>();
}
@Override
public String serialize(Object value) {
if (value == null) return null;
try {
return mapper.writeValueAsString(value);
} catch (Exception e) {
throw new SerDesException(
"Serialization failed for type: " + value.getClass().getName(), e);
}
}
@Override
public <T> T deserialize(String data, TypeToken<T> typeToken) {
if (data == null) return null;
try {
// Convert TypeToken to Jackson's JavaType using TypeFactory
// Cache to avoid repeated reflection overhead
JavaType javaType = typeCache.computeIfAbsent(typeToken.getType(), type -> typeFactory.constructType(type));
return mapper.readValue(data, javaType);
} catch (Exception e) {
throw new SerDesException(
"Deserialization failed for type: " + typeToken.getType().getTypeName(), e);
}
}
}