Mapping a dictionary to an array isn't difficult. You have a few options, the map(_:) method being my personal favorite for most situations. Converting an array to a dictionary is less trivial. Swift's Standard Library doesn't seem to offer an API to make this straightforward. In this episode, I show you two solutions.

An Example

Before I show you the two solutions I have in mind, we need an example. We define a struct with name AnalyticsClient. The AnalyticsClient struct is responsible for sending events to a third party analytics service. The API of the AnalyticsClient struct is simple, a single method that accepts an Event object and zero or more Property objects as arguments.

struct AnalyticsClient {

    func send(event: Event, properties: Property...) {
    	
    }

}

Event is an enum that defines the events the analytics client supports. The raw value of each case is the name of the event.

enum Event: String {

    // MARK: - Cases

    case viewedProduct = "Viewed Product"
    case purchasedProduct = "Purchased Product"

}

Property is also an enum. Each case defines a property of an event and we use associated values to define the value of each property. The Property enum defines a computed key property and a computed value property.

enum Property {

    // MARK: - Cases

    case product(id: String)
    case price(Float)
    case source(String)

    // MARK: - Properties

    var key: String {
        switch self {
        case .product:
            return "product"
        case .price:
            return "price"
        case .source:
            return "source"
        }
    }

    var value: String {
        switch self {
        case .product(id: let id):
            return id
        case .price(let price):
            return "\(price)"
        case .source(let source):
            return source
        }
    }

}

Converting an Array to a Dictionary

The send(event:properties:) method accepts zero or more Property objects. The third party analytics service expects a dictionary of key-value pairs so we need to convert the sequence of properties to a dictionary of type [String: String].

The first solution is the most obvious one. We create a mutable dictionary of type [String: String] and iterate through the sequence of properties that are passed to the send(event:properties:) method. We use the computed key and valueproperties of theProperty` enum to update the dictionary.

var eventProperties = [String: String]()

properties.forEach { property in
    eventProperties[property.key] = property.value
}

While there isn't anything inherently wrong with this approach, I prefer another solution that is a little bit cleaner and requires less ceremony. We invoke the reduce(into:_:) method on the sequence of properties. The reduce(into:_:) method defines two parameters, the initial result and a closure that is invoked for each element of the sequence.

Because we want to convert the sequence of properties to a dictionary, the initial result is a dictionary of type [String: String]. The second argument of the reduce(into:_:) method is a closure that accepts the result and an element of the sequence. In the closure, we update the dictionary just like we did in the first solution. We assign the result of the reduce(into:_:) method to a constant.

let eventProperties = properties.reduce(into: [String: String]()) { result, property in
    result[property.key] = property.value
}

Both solutions are valid, but the solution that leverages the reduce(into:_:) method is arguably cleaner and doesn't require eventProperties to be mutable, that is, a variable.

Performance

Which solution you choose is up to your preference. The performance tests I ran indicate there is no significant difference in performance. I prefer the second solution as it is more concise and doesn't require a variable.

final class AnalyticsClientTests: XCTestCase {

    func testPerformanceForEach() throws {
        let properties = (0...1_000_000).map {
            Property.product(id: "\($0)")
        }

        self.measure {
            var dictionary = [String: String]()
            properties.forEach { property in
                dictionary[property.key] = property.value
            }
        }
    }

    func testPerformanceReduce() throws {
        let properties = (0...1_000_000).map {
            Property.product(id: "\($0)")
        }

        self.measure {
            _ = properties.reduce(into: [String: String]()) { result, property in
                result[property.key] = property.value
            }
        }
    }

}

How to Map an Array to a Dictionary