Mapping Firestore Data in Swift
The Comprehensive Guide
Swift’s Codable API, introduced in Swift 4, enables us to leverage the power of the compiler to make it easier to map data from serialised formats to Swift types.
You might have been using Codable to map data from a web API to your app’s data model (and vice versa), but it is much more flexible than that.
In this article, we’re going to look at how Codable can be used to map data from Firestore to Swift types and vice versa.
If you’re new to Cloud Firestore - it is a horizontally scaling NoSQL document database in the cloud, part of Firebase’s Backend-as-a-Service products. Data is stored in documents that contain fields mapping to values. Documents are stored in collections, which you can think of as containers that you can use to organise your data. Documents support many data types such as strings and numbers, but also complex, nested objects. You can even create subcollections within documents, and build hierarchical data structures.
When fetching a document from Firestore, your app will receive a dictionary of key/value pairs (or an array of dictionaries, if you use one of the operations returning multiple documents).
Now, you can certainly use dictionaries in Swift, and they offer some great flexibility that might be exactly what your use case calls for. However, this approach isn’t type-safe and it’s easy to introduce hard-to-track-down bugs by misspelling attribute names, or forgetting to map that new attribute your team added when they shipped that exciting new feature last week.
In the past, many developers have worked around these shortcomings by implementing a simple mapping layer that allowed them to map dictionaries to Swift types. But again, most of these implementations are based on manually specifying the mapping between Firestore documents and the corresponding types of your app’s data model.
With Firestore’s support for Swift’s Codable API, this becomes a lot easier:
- You will no longer have to manually implement any mapping code
- It’s easy to define how to map attributes with different names
- It has built-in support for many of Swift’s types
- And it’s easy to add support for mapping custom types
And - best of all: for simple data models, you won’t have to write any mapping code at all.
Cloud Firestore stores data in documents which map keys to values. To fetch data from an individual document, we can call
DocumentSnapshot.data(), which returns a dictionary mapping the field names to an
Any: func data() -> [String : Any]?.
This means we can use Swift’s subscript syntax to access each individual field, like this:
While it might seem straightforward and easy to implement, this code is fragile, hard to maintain, and error-prone. As you can see, we’re making assumptions about the data types of the document fields . These might or might not be correct (remember, as there is no schema, you can easily add a new document to the collection and choose a different type for a field - you might accidentally choose
string for the
numberOfPages field, which would result in a difficult-to-find mapping issue). Also, you’ll have to update your mapping code whenever a new field is added, which is rather cumbersome. And let’s not forget that we’re not taking advantage of Swift’s strong type system, which knows exactly what’s the correct type for each of the properties of
So, please don’t map your documents manually - use Codable instead!
What is Codable, anyway?
According to Apple’s documentation, Codable is “a type that can convert itself into and out of an external representation.” In fact,
Codable is a type alias for the
Decodable protocols. By conforming a Swift type to this protocol, the compiler will synthesise the code needed to encode/decode an instance of this type from a serialised format, such as JSON.
A simple type for storing data about a book might look like this:
As you can see, conforming the type to
Codable is minimally invasive - we only had to add the conformance to the protocol, no other changes are required.
With this in place, we can now easily encode a book to a JSON object:
Decoding a JSON object to a
Book instance works as follows:
For more details about
Codable, I recommend the following resources:
- John Sundell has a nice article about the Basics of Codable
- If books are more your thing, check out Mattt’s Flight School Guide to Swift Codable
- And finally, Donny Wals has an entire series about Codable.
Mapping simple types using Codable
Firestore supports a broad set of data types, ranging from simple strings to nested maps. Most of these correspond directly to Swift’s built-in types. Let’s take a look at mapping some simple data types first before we dive into the more complex ones.
To map Firestore documents to Swift types, follow these steps:
FirebaseFirestoreSwiftto your project. You can use either CocoaPods or the Swift Package Manager to do so.
- Conform your type to
- Add an
idproperty to your type, and use
@DocumentIDto tell Firestore to map this to the document ID
documentReference.data(as: )to map a document reference to a Swift type
documentReference.setData(from: )to map data from Swift types to a Firestore document
- (optional, but highly recommended) Implement proper error handling
Let’s update our
Book type accordingly:
Since this type was already codable, we only had to add the
id property and annotate it with the
@DocumentID property wrapper.
Taking the previous code snippet for fetching and mapping a document, we can replace all the manual mapping code with a single line:
You can even write this more concisely by specifying the type of the document when calling
getDocument(as:). This will perform the mapping for you, and return a
Result type containing the mapped document or an error in case decoding failed:
Updating an existing document is as simple as calling
documentReference.setData(from: ). Including some basic error handling, here is the code to save a
When adding a new document, Firestore will automatically take care of assigning a new document ID to the document. This also works when the app is currently offline.
In addition to mapping simple data types, Firestore supports a number of other datatypes, some of which are structured types that you can use to create compound types inside a document.
Mapping nested custom types
Most attributes we want to map in our documents are simple values, such as the book’s title or the author’s name. But what about those cases when we need to store a more complex object? For example, we might want to store the URLs to the book’s cover in different resolutions.
The easiest way to do this in Firestore is to use a map:
When writing the corresponding Swift
struct, we can make use of the fact that Firestore supports URLs - when storing a field that contains a URL, it will be converted to a
string and vice versa (see FirestoreEncoder.swift):
Notice how we defined a struct,
CoverImages , for the
cover map in the Firestore document. By marking the
cover property on
BookWithCoverImages as optional, we’re able to handle the fact that some documents might not contain a
If you’re curious why there is no code snippet for fetching or updating data, you will be pleased to hear that there is no need to adjust the code for reading or writing from/to Firestore: all of this works with the code we’ve written in the initial section.
Sometimes, we want to store a collection of values in a document. The genres of a book are a good example: a book like The Hitchhiker’s Guide to the Galaxy might fall into several categories - in this case Sci-Fi and Comedy:
In Firestore, we can model this using an
array of values. This is supported for any codable types (such as
Int, etc.). The following shows how to add an array of genres to our
Since this works for any codable type, we can use custom types as well. Imagine we’d want to store a list of tags for each book. Along with the name of the tag, we’d like to store the color of the tag as well, like this:
To store tags in this way, all we need to do is implement a
Tag struct to represent a tag and make it codable:
And just like that, we can store an array of
Tags in our
A quick word about mapping document IDs
Before we move on to more mapping more types, let’s talk about mapping document IDs for a moment.
We’ve used the
@DocumentID property wrapper in some of the previous examples to map the document ID of our Firestore documents to the
id property of our Swift types. This is important for a number of reasons: one, it helps us to know which document to update in case the user makes local changes. And two, SwiftUI’s
List requires its elements to be
Identifiable in order to prevent elements from jumping around when they get inserted.
It’s worth pointing out that an attribute marked as
@DocumentID will not be encoded by Firestore’s encoder when writing the document back. This is because the document ID is not an attribute of the document itself - so writing it to the document would be a mistake.
When working with nested types (such as the array of tags on the
Book in an earlier example in this article), it is not required to add an
@DocumentID property: nested properties are a part of the Firestore document, and do not constitute a separate document. Hence, they do not need a document ID.
Mapping dates and times
Firestore has a built-in data type for handling dates and times, and - thanks to Firestore’s support for Codable, it’s straightforward to use them.
Let’s take a look at this document which represents the mother of all programming languages, Ada - invented in 1843:
A Swift type for mapping this document might look like this:
We cannot leave this section about dates and times without having a conversation about
@ServerTimestamp. This property wrapper is a powerhouse when it comes to dealing with timestamps in your app.
In any distributed system, chances are that the clocks on the individual systems are not completely in sync all of the time. You might think this is not a big deal, but imagine the implications of a clock running slightly out of sync for a stock trade system: even a millisecond deviation might result in a difference of millions of dollars when executing a trade.
Firestore handles attributes marked with
@ServerTimestamp as follows: if the attribute is
nil when you store it (using
addDocument, for example), Firestore will populate the field with the current server timestamp at the time of writing it into the database. If the field is not
nil when you call
updateData(), Firestore will leave the attribute value untouched. This way, it is easy to implement fields like
One of the most magical experiences when I got my first smartphone was to be able to pinpoint my exact location on a map and find the nearest café. Many exciting features become possible by storing geolocations. For example, it might be useful to store a location for a task so your app can remind you about a task when you reach a destination.
Firestore has a built-in data type GeoPoint that can store the longitude and latitude of any location. To map locations from / to a Firestore document, we can use the
GeoPoint to a
CLLocationCoordinate2D is a straight-forward operation:
To learn more about querying documents by physical location, check out this solution guide.
“How can I map colors” is one of the most frequently asked questions, not only for Firestore, but for mapping between Swift and JSON as well. There are plenty of solutions out there, but most of them focus on JSON, and almost all of them map colors as a nested dictionary composed of its RGB components.
I’ve been mildly dissatisfied with all of these solutions and always felt there should be a better, simpler solution. Why don’t we use web colors (or, to be more specific, CSS hex color notation) - they’re easy to use (essentially just a string), and they even support transparency!
To be able to map a Swift
Color to its hex value, we need to create a Swift extension that adds
decoder.singleValueContainer(), we can decode a
String to its
Color equivalent, without having to nest the RGBA components. Plus, you can use these values in the web UI of your app, without having to convert them first!
With this, we can update code for mapping tags, making it easier to handle the tag colors directly instead of having to map them manually in our app’s UI code:
Enums are probably one of the most underrated language features in Swift - there’s much more to them than meets the eye. A common use case for enums is to model the discrete states of something. For example, we might be writing an app for managing articles. To track the status of an article, we might want to use an enum
Firestore doesn’t support enums natively (i.e. it cannot enforce the set of values), but we can still make use of the fact that enums can be typed, and choose a codable type. In this example, we’ve chosen
String , which means all enum values will be mapped to/from
string when stored in a Firestore document.
And, since Swift supports custom raw values, we can even customise which values refer to which enum case. So for example, if we decided to store the
Status.inReview case as
in review, we could just update the above enum as follows:
Customising the mapping
Sometimes, the attribute names of the Firestore documents we want to map don’t match up with the names of the properties of our data model in Swift. For example, one of our coworkers might be a Python developer, and decided to choose
snake_case for all their attribute names 🐍.
Not to worry - Codable has us covered!
For cases like these, we can make use of
CodingKeys . This is an
enum we can add to a codable struct to specify how certain attributes will be mapped.
Consider this document:
To map this document to a
struct that has a
name property of type
String, we need to add a
CodingKeys enum to the
ProgrammingLanguage struct, and specify the name of the attribute in the document:
By default, the Codable API will use the property names of our Swift types to determine the attribute names on the Firestore documents we’re trying to map. So as long as the attribute names match, there is no need to add
CodingKeys to our codable types. However, once we use
CodingKeys for a specific type, we need to add all property names we want to map. Any property that is not listed as a case on the respective
CodingKeys enum will be ignore during the mapping process.
This can actually be convenient if we specifically want to exclude some of the properties from being mapped.
So for example, if we want to exclude the
reasonWhyILoveThis property from being mapped, all we need to do is to remove it from the
Occasionally we might want to write an empty attribute back into the Firestore document. Swift has the notion of optionals to denote the absence of a value, and Firestore supports
null values as well. However, the default behaviour for encoding optionals that have a
nil value is to just omit them.
@ExplicitNull gives us some control over how Swift optionals are handled when encoding them: by flagging an optional property as
@ExplicitNull, we can tell Firestore to write this property to the document with a
null value if it contains a value of
In the above code snippets I intentionally kept error handling at a minimum, but in any production app, you’ll want to make sure to gracefully handle any errors.
Here is a code snippet that show how to use handle any error situations you might run into:
The previous code snippet demonstrates how to handle errors when fetching a single document. In addition to fetching data once, Firestore also supports delivering updates to your app as they happen, using so-called snapshot listeners: we can register a snapshot listener on a collection (or query), and Firestore will call our listener whenever there is an update.
Here is a code snippet that show how to register a snapshot listener, map data using Codable, and handle any errors that might occur. It also shows how to add a new document to the collection. As you will see, there is no need to update the local array holding the mapped documents ourselves, as this is taken care of by the code in the snapshot listener.
All code snippets used in this post are part of a sample application that you can download from this GitHub repository.
Go forth and use Codable!
Swift’s Codable API provides a powerful and flexible way to map data from serialised formats to and from your applications data model. In this article, you saw how easy it is to use in apps that use Firestore as their data store.
Starting from a basic example with simple data types, we progressively increased the complexity of the data model, all the while being able to rely on Codable and Firebase’s implementation to perform the mapping for us.
There really is no reason for not using Firestore’s Codable support.
Thanks for reading! 🔥