Search code examples
c#jsongoogle-cloud-firestorejson.nettimestamp

Converting Firestore Timestamp to JSON (Newtonsoft) returns empty object


I am reading data from a Firestore database and converting it to JSON using Newtonsoft in a C# Web API. However, the timestamp fields always convert to an empty object.

  ds is a DocumentSnapshot
  Dictionary<string, object> d = ds.ToDictionary();
  JToken jt = JToken.FromObject(d);

The above returns an empty object for the "date" field, which is a Firestore Timestamp:

date: {}

I have done the following as a workaround:

  var dt  = d.GetValueOrDefault("date");
  if (dt != null)
  {
    var dts = dt.ToString().Remove(0, 11);
    d["date"] = dts;
  }

I have tried the various date options for the conversion but none of them do anything.


Solution

  • The type Google.Cloud.Firestore.Timestamp has no public properties, which is why it is serialized as an empty object {}. To serialize it as a RFC 3339 UTC Timestamp string you will need to create a custom JsonConverter<Timestamp>:

    public class TimestampConverter : JsonConverter<Google.Cloud.Firestore.Timestamp> // namespace added for clarity
    {
        //https://cloud.google.com/dotnet/docs/reference/Google.Cloud.Firestore/latest/Google.Cloud.Firestore.Timestamp
        public override Timestamp ReadJson(JsonReader reader, Type objectType, Timestamp existingValue, bool hasExistingValue, JsonSerializer serializer) =>
            Timestamp.FromDateTime(serializer.Deserialize<DateTime>(reader).ToUniversalTime());
    
        public override void WriteJson(JsonWriter writer, Timestamp value, JsonSerializer serializer) =>
            writer.WriteRawValue(JsonConvert.ToString(value.ToDateTime(), DateFormatHandling.IsoDateFormat, DateTimeZoneHandling.Utc));
    }
    

    Alternatively, if you need the generated JSON Timestamp values to adhere precisely to Google's expected RFC 3339 format, you may use Google.Apis.Json.RFC3339DateTimeConverter to format the values:

    public class TimestampConverter : JsonConverter<Google.Cloud.Firestore.Timestamp> // namespace added for clarity
    {
        //https://cloud.google.com/dotnet/docs/reference/Google.Apis/latest/Google.Apis.Json.RFC3339DateTimeConverter
        //A JSON converter which honers RFC 3339 and the serialized date is accepted by Google services.
        static readonly Google.Apis.Json.RFC3339DateTimeConverter googleDateTimeConverter = new();
        
        //https://cloud.google.com/dotnet/docs/reference/Google.Cloud.Firestore/latest/Google.Cloud.Firestore.Timestamp
        public override Timestamp ReadJson(JsonReader reader, Type objectType, Timestamp existingValue, bool hasExistingValue, JsonSerializer serializer) =>
            Timestamp.FromDateTime(serializer.Deserialize<DateTime>(reader).ToUniversalTime());
    
        public override void WriteJson(JsonWriter writer, Timestamp value, JsonSerializer serializer) =>
            googleDateTimeConverter.WriteJson(writer, value.ToDateTime(), serializer);
    }
    

    Either way, to serialize to JToken using the following settings:

    var settings = new JsonSerializerSettings
    {
        Converters = { new TimestampConverter() },
    };
    var jt = JToken.FromObject(d, JsonSerializer.Create(settings));
    

    Or to a JSON string as follows:

    var json = JsonConvert.SerializeObject(d, settings);
    

    The resulting JSON for your dictionary d will look like:

    {"date":"2023-04-11T21:01:01.110101Z"}
    

    For the first converter, or

    {"date":"2023-04-11T21:08:01.110Z"}
    

    For the second. (The difference seems only to be in the precision.)

    Demo fiddles here and here.