Preview for struct with custom codable initializer

I need to create a valid preview for UserView, which means I need to parse in an single User, which struct looks like this:

struct User: Codable, Hashable {
    var id: UUID
    var isActive: Bool
    var name: String
    var age: Int
    var company: String
    var email: String
    var address: String
    var about: String
    var registered: Date
    var tags: [String]
    var friends: [Friend]

    enum CodingKeys: String, CodingKey {
        case id
        case isActive
        case name
        case age
        case company
        case email
        case address
        case about
        case registered
        case tags
        case friends
        }
    
    init(from decoder: any Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let dateFormatter = ISO8601DateFormatter()
        
        let stringId = try container.decode(String.self, forKey: .id)
        self.id = UUID(uuidString: stringId) ?? UUID()
        
        self.isActive = try container.decode(Bool.self, forKey: .isActive)
        self.name = try container.decode(String.self, forKey: .name)
        self.age = try container.decode(Int.self, forKey: .age)
        self.company = try container.decode(String.self, forKey: .company)
        self.email = try container.decode(String.self, forKey: .email)
        self.address = try container.decode(String.self, forKey: .address)
        self.about = try container.decode(String.self, forKey: .about)
        
        let stringRegistered = try container.decode(String.self, forKey: .registered)
        self.registered = dateFormatter.date(from: stringRegistered) ?? Date()
        
        self.tags = try container.decode([String].self, forKey: .tags)
        self.friends = try container.decode([Friend].self, forKey: .friends)
    }
    
    func encode(to encoder: any Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        
        try container.encode(self.id.uuidString, forKey: .id)
        try container.encode(self.isActive, forKey: .isActive)
        try container.encode(self.name, forKey: .name)
        try container.encode(self.age, forKey: .age)
        try container.encode(self.company, forKey: .company)
        try container.encode(self.email, forKey: .email)
        try container.encode(self.address, forKey: .address)
        try container.encode(self.about, forKey: .about)
        try container.encode(self.registered.ISO8601Format(), forKey: .registered)
        try container.encode(self.tags, forKey: .tags)
        try container.encode(self.friends, forKey: .friends)
    }
}

The simple approach
UserView(user: User(id: XXX, name: XXX ...))
does not work here, because of the custom codable initializer (which I need of course).

That's what Xcode suggests/expects:
UserView(user: User(from: <any Decoder> ))

The general code works perfectly fine, I just want to use the preview to design the UserView.

How can I create a valid User for preview?

You could move your codable implementation to an extension (along with conformance to codable) - if you don’t have an initialiser in the main body of a type - it’s default memberwise initialiser synthesising won’t be suppressed.

Aside from that you may probably remove most/all of your custom coding (dateEncodingStrategy might help).

1 Like

Thanks - will try the extension soon! For now I just created a "default" initialiser manually.

What do you mean by

remove most/all of your custom coding

I thought if I have to use a custom codable initialiser because of some values (registered, id) then the default code for the other variables is also needed?

Regarding dateEncodingStrategy, where should I use this? If I would create a JSONEncoder / Decoder for this in the User struct, that would mean the JSONDecoder that uses the init has another JSONDecoder inside, is that possible?

How would you do that?

I don't see the custom implementation is doing anything significantly different compared to the built-in implementation for those two mentioned fields: UUID's are automatically translated to/from strings and with date formatting strategy you could achieve iso8601 format:

import Foundation

struct Friend: Codable, Hashable {}

struct User: Hashable, Codable {
    var id: UUID
    var isActive: Bool
    var name: String
    var age: Int
    var company: String
    var email: String
    var address: String
    var about: String
    var registered: Date
    var tags: [String]
    var friends: [Friend]
}

var date = Date()
date = Date(timeIntervalSince1970: date.timeIntervalSince1970.rounded()) // see below why
let uuid = UUID()

let user = User(id: uuid, isActive: true, name: "my name", age: 42, company: "my ltd", email: "my@email", address: "my address", about: "about me", registered: date, tags: ["tag1", "tag2"], friends: [Friend()])

let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
let data = try! encoder.encode(user)
let s = String(data: data, encoding: .utf8)!
print(s)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let user2 = try! decoder.decode(User.self, from: data)
precondition(user == user2)

One caveat: I had to round the date to be of integer seconds, otherwise converting to and from iso8601 form would lose fractions and the results would not compare equal.