SwiftData with Codable

294 Views Asked by At

I'm attempting to integrate SwiftData into my networking model to incorporate local persistence. In order to achieve this, I need to ensure that my Codable model conforms to SwiftData. However, I encounter an error during the build process. Has anyone else encountered a similar issue and found a resolution?

enter image description here

Below are my models: I've omitted certain model properties to enable the post's release.


import SwiftData
import SwiftUI

// MARK: - Game
@Model
class Game: Codable, Identifiable, Hashable {
    
    let id: Int?
    let name: String?
    let cover: Cover?
    let firstReleaseDate: Int?
    let summary: String?
    let totalRating: Double?
    let ratingCount: Int?
    let genres: [Genre]?
    let platforms: [Platform]?
    let releaseDates: [ReleaseDate]?
    let screenshots: [Cover]?
    let gameModes: [GameMode]?
    let videos: [Video]?
    let websites: [Website]?
    let similarGames: [Int]?
    let artworks: [Artwork]?
    
    enum CodingKeys: String, CodingKey {
        case id, name, cover, artworks, genres, platforms, screenshots, summary, videos, websites
        case firstReleaseDate = "first_release_date"
        case releaseDates = "release_dates"
        case totalRating = "total_rating"
        case ratingCount = "rating_count"
        case gameModes = "game_modes"
        case similarGames = "similar_games"
    }
    
    init(
        id: Int? = nil,
        name: String? = nil,
        cover: Cover? = nil,
        firstReleaseDate: Int? = nil,
        summary: String? = nil,
        totalRating: Double? = nil,
        versionTitle: String? = nil,
        ratingCount: Int? = nil,
        genres: [Genre]? = nil,
        platforms: [Platform]? = nil,
        releaseDates: [ReleaseDate]? = nil,
        screenshots: [Cover]? = nil,
        gameModes: [GameMode]? = nil,
        videos: [Video]? = nil,
        websites: [Website]? = nil,
        similarGames: [Int]? = nil,
        artworks: [Artwork]? = nil
    ) {
        self.id = id
        self.name = name
        self.cover = cover
        self.firstReleaseDate = firstReleaseDate
        self.summary = summary
        self.totalRating = totalRating
        self.ratingCount = ratingCount
        self.genres = genres
        self.platforms = platforms
        self.releaseDates = releaseDates
        self.screenshots = screenshots
        self.gameModes = gameModes
        self.videos = videos
        self.websites = websites
        self.similarGames = similarGames
        self.artworks = artworks
    }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.id = try container.decode(Int?.self, forKey: .id)
        self.name = try container.decode(String?.self, forKey: .name)
        self.firstReleaseDate = try container.decode(Int?.self, forKey: .firstReleaseDate)
        self.summary = try container.decode(String?.self, forKey: .summary)
        self.totalRating = try container.decode(Double?.self, forKey: .totalRating)
        self.genres = try container.decode([Genre]?.self, forKey: .genres)
        self.platforms = try container.decode([Platform]?.self, forKey: .platforms)
        self.releaseDates = try container.decode([ReleaseDate]?.self, forKey: .releaseDates)
        self.screenshots = try container.decode([Cover]?.self, forKey: .screenshots)
        self.gameModes = try container.decode([GameMode]?.self, forKey: .gameModes)
        self.videos = try container.decode([Video]?.self, forKey: .videos)
        self.websites = try container.decode([Website]?.self, forKey: .websites)
        self.similarGames = try container.decode([Int]?.self, forKey: .similarGames)
        self.artworks = try container.decode([Artwork]?.self, forKey: .artworks)
        self.cover = try container.decode(Cover?.self, forKey: .cover)
        self.ratingCount = try container.decode(Int?.self, forKey: .ratingCount)
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encode(cover, forKey: .cover)
        try container.encode(artworks, forKey: .artworks)
        try container.encode(firstReleaseDate, forKey: .firstReleaseDate)
        try container.encode(genres, forKey: .genres)
        try container.encode(name, forKey: .name)
        try container.encode(platforms, forKey: .platforms)
        try container.encode(releaseDates, forKey: .releaseDates)
        try container.encode(screenshots, forKey: .screenshots)
        try container.encode(summary, forKey: .summary)
        try container.encode(totalRating, forKey: .totalRating)
        try container.encode(ratingCount, forKey: .ratingCount)
        try container.encode(gameModes, forKey: .gameModes)
        try container.encode(videos, forKey: .videos)
        try container.encode(websites, forKey: .websites)
        try container.encode(similarGames, forKey: .similarGames)
    }
}

// MARK: - Artwork
@Model
class Artwork: Codable, Hashable {
    let id: Int?
    let url: String?
    
    enum CodingKeys: CodingKey {
        case id
        case url
    }
    
    init(id: Int?, url: String?) {
        self.id = id
        self.url = url
    }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.id = try container.decode(Int?.self, forKey: .id)
        self.url = try container.decode(String?.self, forKey: .url)
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encode(url, forKey: .url)
    }
}

// MARK: - Cover
@Model
class Cover: Codable, Hashable {
    let id: Int?
    let url: String?
    
    enum CodingKeys: CodingKey {
        case id
        case url
    }
    
    init(id: Int?, url: String?) {
        self.id = id
        self.url = url
    }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.id = try container.decode(Int?.self, forKey: .id)
        self.url = try container.decode(String?.self, forKey: .url)
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encode(url, forKey: .url)
    }
}

// MARK: - Genre
@Model
class Genre: Codable, Hashable {
    let id: Int?
    let name: String?
    
    enum CodingKeys: CodingKey {
        case id
        case name
    }
    
    init(id: Int?, name: String?) {
        self.id = id
        self.name = name
    }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.id = try container.decode(Int?.self, forKey: .id)
        self.name = try container.decode(String?.self, forKey: .name)
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encode(name, forKey: .name)
    }
}

// MARK: - GameMode
@Model
class GameMode: Codable, Hashable {
    ////
}

// MARK: - Platform
@Model
class Platform: Codable, Hashable {
    ////
}

// MARK: - ReleaseDate
@Model
class ReleaseDate: Codable, Hashable, Comparable {
   ////
}

// MARK: - Video
@Model
class Video: Codable, Hashable {
    ////
}

// MARK: - Website
@Model
class Website: Codable, Hashable {
    //// 
}

Tried to convert a Codable model to conform to Swift Data @Model Macro

2

There are 2 best solutions below

2
lorem ipsum On

SwiftData (or any of the new macros) appears to not be compatible with Codable.

There is no documentation for or against this but the one example where Apple replicates using SwiftData for server data they create an auxiliary struct and initialize with a convenience init.

convenience init(from feature: GeoFeatureCollection.Feature) {
    self.init(
        code: feature.properties.code,
        magnitude: feature.properties.mag,
        time: feature.properties.time,
        name: feature.properties.place,
        longitude: feature.geometry.coordinates[0],
        latitude: feature.geometry.coordinates[1]
    )
}

https://developer.apple.com/documentation/swiftdata/maintaining-a-local-copy-of-server-data

0
Soumya Mahunt On

The current compiler Codable generation requires all the class properties to conform to Codable as well. But applying @Model macro generates some class properties that don't conform to Codable, hence you see this error. Here are some of the options you have:

  1. Create the implementation manually, which you are already doing.
  2. Look for external macro solution that provide Codable conformance, I created MetaCodable macro library that provides Codable conformance with bunch of features:
@Codable
@Model
class Genre {
    let id: Int?
    let name: String?
    
    init(id: Int?, name: String?) {
        self.id = id
        self.name = name
    }
}