Search code examples
swiftcodableswift-structs

How can I init a struct from the values of a different struct


I have a user profile I am storing with a struct shaped like

struct Profile: Codable {

    let company: String?
    let country: String?
    let createdDate: String?
    let dateOfBirth: String?
    let department: String?
    let email: String?
    let employeeKey: String?
    let firstName: String?
    let gender: String?
    let id: String?
    let jobTitle: String?
    let lastName: String?
    let location: String?
    let mobileDeviceToken: String?
    let pictureUri: String?
    let roles: [String]?
    let status: String?
    let updatedDate: String?
    let userId: String?
    let webDeviceToken: String?
    let webMobileDeviceToken: String?

    enum CodingKeys: String, CodingKey {
        case company = "company"
        case country = "country"
        case createdDate = "createdDate"
        case dateOfBirth = "dateOfBirth"
        case department = "department"
        case email = "email"
        case employeeKey = "employeeKey"
        case firstName = "firstName"
        case gender = "gender"
        case id = "id"
        case jobTitle = "jobTitle"
        case lastName = "lastName"
        case location = "location"
        case mobileDeviceToken = "mobileDeviceToken"
        case pictureUri = "pictureUri"
        case roles = "roles"
        case status = "status"
        case updatedDate = "updatedDate"
        case userId = "userId"
        case webDeviceToken = "webDeviceToken"
        case webMobileDeviceToken = "webMobileDeviceToken"
    }
}

I have another struct which looks like

struct ArticleAuthor {
    let name: String
    let department: String
    let email: String
}

When fetching a user profile, I'd like to be able to create my ArticleAuthor struct using the Profile object returned from my Profile Service.

I was hoping to do something like this, but it does not work as the from value is expected to be data.

        self?.profileService.fetchForUserByUserId(userId: authorId) { [weak self] profile, error in
            guard error == nil else { return }

            let author = try? JSONDecoder().decode(ArticleAuthor.self, from: profile)

                print(author) // should be a populated author property

        }

I was hoping to avoid something like let author = ArticleAuthor(name: profile?.firstName, department: profile?.department, email: profile?.email) as this object could grow in time.


Solution

  • The profile object in your sample code is already 'Decoded', so you dont need to decode it again.

    To avoid using the default init, you can just add a custom initializer, so that you can pass in a Profile struct and set the values. This is usually the best way to go about it as it prevents making lots of changes throughout the codebase when you add new properties

    struct ArticleAuthor {
        let name: String?
        let department: String?
        let email: String?
    
        init(profile: Profile) {
            self.name = profile.firstName
            self.department = profile.department
            self.email = profile.email
        }
    }
    
    self?.profileService.fetchForUserByUserId(userId: authorId) { [weak self] profile, error in
        guard error == nil else { return }
    
        let author = Author(profile: profile)
        print(author) // should be a populated author property
    }