Description
I use dapr v1.0.0. I wrote the actor use Java SDK. To use the right OffsetDateTime class serialization I wrote a custom Json serializer class ISO8601DaprObjectSerializer extends ObjectSerializer implements DaprObjectSerializer
.
Expected Behavior
When I use this serializer I want to see JSON in state store
Actual Behavior
Now in state store I see base64 encoded string. In this case, I should use a special deserialized object constructor with string parameter.
Steps to Reproduce the Problem
When register actor add custom serializer
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import io.dapr.client.ObjectSerializer;
import io.dapr.client.domain.CloudEvent;
import io.dapr.serializer.DaprObjectSerializer;
import io.dapr.utils.TypeRef;
import java.io.IOException;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.regex.Pattern;
public class ISO8601DaprObjectSerializer extends ObjectSerializer implements DaprObjectSerializer {
private static final Pattern PATTERN_ISO8601 = Pattern.compile("^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(\\.[0-9]+)?(Z|[+-](?:2[0-3]|[01][0-9]):[0-5][0-9])?$");
private static final DateTimeFormatter ISO8601 = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX").withZone(ZoneId.of("UTC"));
private static final JavaTimeModule TIME_MODULE = new JavaTimeModule();
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true)
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.registerModule(TIME_MODULE);
static {
TIME_MODULE.addSerializer(OffsetDateTime.class, new JsonSerializer<>() {
@Override
public void serialize(OffsetDateTime offsetDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeString(offsetDateTime.format(ISO8601));
}
});
}
@Override
public byte[] serialize(Object state) throws IOException {
if (state == null) {
return null;
}
if (state.getClass().equals(Void.class)) {
return null;
}
// Have this check here to be consistent with deserialization (see deserialize() method below).
if (state instanceof byte[]) {
return (byte[]) state;
}
// Not string, not primitive, so it is a complex type: we use JSON for that.
return OBJECT_MAPPER.writeValueAsBytes(state);
}
@Override
public <T> T deserialize(byte[] content, TypeRef<T> type) throws IOException {
var javaType = OBJECT_MAPPER.constructType(type.getType());
if ((javaType == null) || javaType.isTypeOrSubTypeOf(Void.class)) {
return null;
}
if (javaType.isPrimitive()) {
return deserializePrimitives(content, javaType);
}
if (content == null) {
return (T) null;
}
// Deserialization of GRPC response fails without this check since it does not come as base64 encoded byte[].
if (javaType.hasRawClass(byte[].class)) {
return (T) content;
}
if (content.length == 0) {
return (T) null;
}
if (javaType.hasRawClass(CloudEvent.class)) {
return (T) CloudEvent.deserialize(content);
}
return OBJECT_MAPPER.readValue(content, javaType);
}
@Override
public String getContentType() {
return "application/json";
}
/**
* Parses a given String to the corresponding object defined by class.
*
* @param content Value to be parsed.
* @param javaType Type of the expected result type.
* @param <T> Result type.
* @return Result as corresponding type.
* @throws IOException if cannot deserialize primitive time.
*/
private static <T> T deserializePrimitives(byte[] content, JavaType javaType) throws IOException {
if ((content == null) || (content.length == 0)) {
if (javaType.hasRawClass(boolean.class)) {
return (T) Boolean.FALSE;
}
if (javaType.hasRawClass(byte.class)) {
return (T) Byte.valueOf((byte) 0);
}
if (javaType.hasRawClass(short.class)) {
return (T) Short.valueOf((short) 0);
}
if (javaType.hasRawClass(int.class)) {
return (T) Integer.valueOf(0);
}
if (javaType.hasRawClass(long.class)) {
return (T) Long.valueOf(0L);
}
if (javaType.hasRawClass(float.class)) {
return (T) Float.valueOf(0);
}
if (javaType.hasRawClass(double.class)) {
return (T) Double.valueOf(0);
}
if (javaType.hasRawClass(char.class)) {
return (T) Character.valueOf(Character.MIN_VALUE);
}
return null;
}
return OBJECT_MAPPER.readValue(content, javaType);
}
public static ObjectMapper getMapper() {
return OBJECT_MAPPER;
}
}
Release Note
RELEASE NOTE: FIX Serialization bug in SDK where custom JSON serializer is handled as byte[]