I have a class called Resource that maps across to some fairly complex JSON structure that I receive from the API. It has some nested properties and objects that I defined within my class. Everything is working as expected in that I get the JSON back and it gets decoded successfully and I can use all of my properties in my UI, such as resource.name, resource.timezone, etc.
I am now trying to implement SwiftData and as soon as I add the @Model macro to my class file, I immediately get two errors: Type 'Resource' does not conform to protocol 'Decodable' and Type 'Resource' does not conform to protocol 'Encodable'
I imagine this is something to do with an init(from:) not being in my class, but the compiler cannot synthesize the init(from: automatically and I'm stumped. I can't wrap my head around how it can be decoded successfully but now has a problem? Unless I'm fundamentally misunderstanding something.
This is my Resource class:
import Foundation
import SwiftData
@Model
class Resource: Codable, Identifiable {
var id: Int
var creatorId: Int?
var archived: Bool?
var bookable: Bool?
var email: String?
var jobTitle: String?
var notes: String?
var color: String
var name: String
var firstName: String?
var lastName: String?
var image: String?
var images: Images?
var account: RGAccount
var phone: String?
var human: Bool
var minutesPerDay: Int?
var createdAt: String
var updatedAt: String
var lastUpdatedBy: Int?
var url: String?
var resourceType: ResourceType
var timezone: Timezone
var vacationAllowance: Int?
var userId: Int?
var role: String?
var permissions: Permissions?
var availablePeriods: [AvailablePeriod]?
var customAttributes: [String: String]?
var customAvailablePeriods: [CustomAvailablePeriod]?
var selectedCustomFieldOptions: [CustomFieldOption]?
var bookingApproverIds: [Int]?
var bookedClientIds: [Int]?
var bookedProjectIds: [Int]?
var overtimes: [Overtime]?
var historicalTimezones: [HistoricalTimezone]?
enum CodingKeys: String, CodingKey {
case id, archived, bookable, email, notes, color, name, image, images, account, phone, human, url, timezone, overtimes, role, permissions
case creatorId = "creator_id"
case jobTitle = "job_title"
case firstName = "first_name"
case lastName = "last_name"
case minutesPerDay = "minutes_per_day"
case createdAt = "created_at"
case updatedAt = "updated_at"
case lastUpdatedBy = "last_updated_by"
case resourceType = "resource_type"
case vacationAllowance = "vacation_allowance"
case userId = "user_id"
case availablePeriods = "available_periods"
case customAttributes = "custom_attributes"
case customAvailablePeriods = "custom_available_periods"
case selectedCustomFieldOptions = "selected_custom_field_options"
case bookingApproverIds = "booking_approver_ids"
case bookedClientIds = "booked_client_ids"
case bookedProjectIds = "booked_project_ids"
case historicalTimezones = "historical_timezones"
}
init(id: Int, creatorId: Int, archived: Bool, bookable: Bool, email: String, jobTitle: String, notes: String, color: String, name: String, firstName: String, lastName: String, image: String, images: Images, account: RGAccount, phone: String, human: Bool, minutesPerDay: Int, createdAt: String, updatedAt: String, lastUpdatedBy: Int, url: String, resourceType: ResourceType, timezone: Timezone, vacationAllowance: Int, userId: Int, role: String, permissions: Permissions, availablePeriods: [AvailablePeriod], customAttributes: [String: String], customAvailablePeriods: [CustomAvailablePeriod], selectedCustomFieldOptions: [CustomFieldOption], bookingApproverIds: [Int], bookedClientIds: [Int], bookedProjectIds: [Int], overtimes: [Overtime], historicalTimezones: [HistoricalTimezone]) {
self.id = id
self.creatorId = creatorId
self.archived = archived
self.bookable = bookable
self.email = email
self.jobTitle = jobTitle
self.notes = notes
self.color = color
self.name = name
self.firstName = firstName
self.lastName = lastName
self.image = image
self.images = images
self.account = account
self.phone = phone
self.human = human
self.minutesPerDay = minutesPerDay
self.createdAt = createdAt
self.updatedAt = updatedAt
self.lastUpdatedBy = lastUpdatedBy
self.url = url
self.resourceType = resourceType
self.timezone = timezone
self.vacationAllowance = vacationAllowance
self.role = role
self.permissions = permissions
self.userId = userId
self.availablePeriods = availablePeriods
self.customAttributes = customAttributes
self.customAvailablePeriods = customAvailablePeriods
self.selectedCustomFieldOptions = selectedCustomFieldOptions
self.bookingApproverIds = bookingApproverIds
self.bookedClientIds = bookedClientIds
self.bookedProjectIds = bookedProjectIds
self.overtimes = overtimes
self.historicalTimezones = historicalTimezones
}
}
class Permissions: Codable {
var resources: String?
var bookings: String?
var projects: String?
var clients: String?
var reports: String?
var downtimes: String?
init(resources: String, bookings: String, projects: String, clients: String, reports: String, downtimes: String) {
self.resources = resources
self.bookings = bookings
self.projects = projects
self.clients = clients
self.reports = reports
self.downtimes = downtimes
}
}
class HistoricalTimezone: Codable {
var timezone: String?
var lastValidDate: String?
enum CodingKeys: String, CodingKey {
case timezone
case lastValidDate = "last_valid_date"
}
init(timezone: String, lastValidDate: String) {
self.timezone = timezone
self.lastValidDate = lastValidDate
}
}
class Overtime: Codable {
var createdAt: String?
var creatorId: Int?
var date: String?
var duration: Int?
var id: Int?
var resourceId: Int?
var updatedAt: String?
enum CodingKeys: String, CodingKey {
case date, duration, id
case createdAt = "created_at"
case creatorId = "creator_id"
case resourceId = "resource_id"
case updatedAt = "updated_at"
}
init(createdAt: String, creatorId: Int, date: String, duration: Int, id: Int, resourceId: Int, updatedAt: String) {
self.createdAt = createdAt
self.creatorId = creatorId
self.date = date
self.duration = duration
self.id = id
self.resourceId = resourceId
self.updatedAt = updatedAt
}
}
class CustomFieldOption: Codable, Identifiable {
var id: Int
var name: String
var value: String
init(id: Int, name: String, value: String) {
self.id = id
self.name = name
self.value = value
}
}
class CustomAvailablePeriod: Codable {
var date: String?
var startTime: Int?
var endTime: Int?
enum CodingKeys: String, CodingKey {
case date
case startTime = "start_time"
case endTime = "end_time"
}
init(date: String, startTime: Int, endTime: Int) {
self.date = date
self.startTime = startTime
self.endTime = endTime
}
}
class CustomAttribute: Codable {
var phone: String?
var capacity: String?
var registrationNumber: String?
enum CodingKeys: String, CodingKey {
case phone, capacity
case registrationNumber = "registration_number"
}
init(phone: String, capacity: String, registrationNumber: String) {
self.phone = phone
self.capacity = capacity
self.registrationNumber = registrationNumber
}
}
class AvailablePeriod: Codable {
var weekDay: Int?
var startTime: Int?
var endTime: Int?
var validFrom: String?
var validUntil: String?
enum CodingKeys: String, CodingKey {
case weekDay = "week_day"
case startTime = "start_time"
case endTime = "end_time"
case validFrom = "valid_from"
case validUntil = "valid_until"
}
init(weekDay: Int, startTime: Int, endTime: Int, validFrom: String, validUntil: String) {
self.weekDay = weekDay
self.startTime = startTime
self.endTime = endTime
self.validFrom = validFrom
self.validUntil = validUntil
}
}
class Images: Codable {
var defaultImage: String?
var tiny: String?
var thumb: String?
var card: String?
var header: String?
var detail: String?
enum CodingKeys: String, CodingKey {
case tiny, thumb, card, header, detail
case defaultImage = "default"
}
init(defaultImage: String, tiny: String, thumb: String, card: String, header: String, detail: String) {
self.defaultImage = defaultImage
self.tiny = tiny
self.thumb = thumb
self.card = card
self.header = header
self.detail = detail
}
}
class RGAccount: Codable {
var id: Int?
var name: String?
var url: String?
init(id: Int, name: String, url: String) {
self.id = id
self.name = name
self.url = url
}
}
class ResourceType: Codable {
var id: Int
var name: String
var url: String
init(id: Int, name: String, url: String) {
self.id = id
self.name = name
self.url = url
}
}
class Timezone: Codable {
var name: String
var offset: Int
init(name: String, offset: Int) {
self.name = name
self.offset = offset
}
}
I've tried to implement an init(from:) initalizer, but with being relatively new to Swift, I cannot get my head around what the actual issue is so I'm poking in the dark. I was under the impression that using Codable meant that it was essentially Decodable and Encodable conformant and because I had no errors/warning before adding @Model I assumed the code was fine.
In my head, I'm imagining it's likely something to do with my custom class types, or maybe the optionals? But they are all basic data types, String, Int, Bool etc. There's no UIImage or anything that doesn't conform to Codable (that I can see), and no previous errors