Lets say we have a Codable object we want to save in Firestore

This object has name, and dateUpdated properties. The dateUpdated property is a Date object. The implementation looks like this:

class Name: Codable {
  var name: String
  var dateUpdated: Date
}

To make my life easier, I’ve added a custom init to this object to convert it from [String: Any] to a Name object.

extension Name {
  init(dictionary: [String: Any]) throws {
    let data = try JSONSerialization.data(withJSONObject: dictionary, options: .prettyPrinted)
    self = try decoder.decode(Self.self, from: data)
  }
}

Pretty easy! Now when I get a response from Firestore I can just convert it to my object like this.

if let document = snapshot.documents.first {
  let data = document.data()
  let name: Name? = try? Name(data)
}

And I have a function that converts the object into a dictionary to save it to firestore.

func dictionary() throws -> [String: Any] {
  let data = try JSONEncoder().encode(self)
  guard let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] else {
    throw NSError()
  }
  return dictionary
}

What’s the problem?

If you try saving an object with a Date property to Firestore, Firestore automatically changes the Date to a Timestamp. This probably wouldn’t be a problem, unless you’re trying to decode that same object where you’ll get the error:

"Invalid type in JSON write (FIRTimestamp)"

This is being caused because Swift’s Foundation doesn’t know how to decode Firestore’s Timestamp type from JSON.

How do you fix it?

It’s pretty simple actually, we just tell the JSONDecoder and JSONEncoder to use a special way of encoding/decoding dates. It’s just a few line’s change for each of the methods in the class.

extension Name {
  init(dictionary: [String: Any]) throws {
    let data = try JSONSerialization.data(withJSONObject: dictionary, options: .prettyPrinted)
    let decoder = JSONDecoder() // Change
    decoder.dateDecodingStrategy = .secondsSince1970 // Change
    self = try decoder.decode(Self.self, from: data) // Change
  }
  
  func dictionary() throws -> [String: Any] {
    let encoder = JSONEncoder() // Change
    encoder.dateEncodingStrategy = .secondsSince1970 // Change    
    let data = try encoder.encode(self)
    guard let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] else {
      throw NSError()
    }
    return dictionary
  }
}

All this is doing is telling swift to save our dateUpdated date as a number instead of an actual date. This way, Firestore doesn’t try to change any Date types to Timestamps and we don’t get the aforementioned error.

The change made was instead of using a bare JSONEncoder or JSONDecoder, I changed it to use this:

let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .secondsSince1970

This is applicable to other environments too. If you’re building a server using swift, you can use those custom JSONEncoder/JSONDecoder date strategies to encode date types so you know what they’ll look like on the server side. The other options for date strategies can be found in the documentation.