71 lines
3.7 KiB
Java
71 lines
3.7 KiB
Java
// src/main/java/com/example/util/FirestoreTimestampDeserializer.java
|
|
package com.example.util;
|
|
|
|
import com.fasterxml.jackson.core.JsonParser;
|
|
import com.fasterxml.jackson.core.JsonToken;
|
|
import com.fasterxml.jackson.databind.DeserializationContext;
|
|
import com.fasterxml.jackson.databind.JsonDeserializer;
|
|
import com.google.cloud.Timestamp;
|
|
import java.io.IOException;
|
|
import java.util.Map;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
/**
|
|
* Custom Jackson Deserializer for com.google.cloud.Timestamp.
|
|
* Handles deserialization from embedded objects (direct Timestamp instances),
|
|
* ISO 8601 strings, and JSON objects with "seconds" and "nanos" fields.
|
|
*/
|
|
public class FirestoreTimestampDeserializer extends JsonDeserializer<Timestamp> {
|
|
|
|
private static final Logger logger = LoggerFactory.getLogger(FirestoreTimestampDeserializer.class);
|
|
|
|
@Override
|
|
public Timestamp deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
|
JsonToken token = p.getCurrentToken();
|
|
|
|
if (token == JsonToken.VALUE_EMBEDDED_OBJECT) {
|
|
// This is the ideal case when ObjectMapper.convertValue gets a direct Timestamp object
|
|
Object embedded = p.getEmbeddedObject();
|
|
if (embedded instanceof Timestamp) {
|
|
logger.debug("FirestoreTimestampDeserializer: Deserializing from embedded Timestamp object: {}", embedded);
|
|
return (Timestamp) embedded;
|
|
}
|
|
} else if (token == JsonToken.VALUE_STRING) {
|
|
// Handles cases where the timestamp is represented as an ISO 8601 string
|
|
String timestampString = p.getText();
|
|
try {
|
|
logger.debug("FirestoreTimestampDeserializer: Deserializing from String: {}", timestampString);
|
|
return Timestamp.parseTimestamp(timestampString);
|
|
} catch (IllegalArgumentException e) {
|
|
logger.error("FirestoreTimestampDeserializer: Failed to parse timestamp string: '{}'", timestampString, e);
|
|
throw new IOException("Failed to parse timestamp string: " + timestampString, e);
|
|
}
|
|
} else if (token == JsonToken.START_OBJECT) {
|
|
// This is crucial for handling the "Cannot deserialize ... from Object value (token JsonToken.START_OBJECT)" error.
|
|
// It assumes the object represents { "seconds": X, "nanos": Y }
|
|
logger.debug("FirestoreTimestampDeserializer: Deserializing from JSON object.");
|
|
|
|
// Suppress the unchecked warning here, as we expect a Map<String, Number>
|
|
@SuppressWarnings("unchecked")
|
|
Map<String, Number> map = p.readValueAs(Map.class);
|
|
|
|
if (map != null && map.containsKey("seconds") && map.containsKey("nanos")) {
|
|
Number secondsNum = map.get("seconds");
|
|
Number nanosNum = map.get("nanos");
|
|
|
|
if (secondsNum != null && nanosNum != null) {
|
|
Long seconds = secondsNum.longValue();
|
|
Integer nanos = nanosNum.intValue();
|
|
return Timestamp.ofTimeSecondsAndNanos(seconds, nanos);
|
|
}
|
|
}
|
|
logger.error("FirestoreTimestampDeserializer: JSON object missing 'seconds' or 'nanos' fields, or fields are not numbers.");
|
|
}
|
|
|
|
// If none of the above formats match, log an error and delegate to default handling
|
|
logger.error("FirestoreTimestampDeserializer: Unexpected token type for Timestamp deserialization. Expected Embedded Object, String, or START_OBJECT. Got: {}", token);
|
|
// This will likely re-throw an error indicating inability to deserialize.
|
|
return (Timestamp) ctxt.handleUnexpectedToken(Timestamp.class, p);
|
|
}
|
|
} |