.
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2025 Google. This software is provided as-is, without warranty or representation for any use or purpose.
|
||||
* Your use of it is subject to your agreement with Google.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user