UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Adding Codable conformance to an @Observable class

Paul Hudson    @twostraws   

If all the properties of a type already conform to Codable, then the type itself can conform to Codable with no extra work – Swift will synthesize the code required to archive and unarchive your type as needed. However, things are a little trickier when working with classes that use the @Observable macro because of the way Swift rewrites our code.

To see the problem in action, we could make a simple observable class that has a single property called name, like this:

@Observable
class User: Codable {
    var name = "Taylor"
}

Now we could write a little SwiftUI code that encodes an instance of our class when a button is pressed, and prints out the resulting text:

struct ContentView: View {
    var body: some View {
        Button("Encode Taylor", action: encodeTaylor)
    }

    func encodeTaylor() {
        let data = try! JSONEncoder().encode(User())
        let str = String(decoding: data, as: UTF8.self)
        print(str)
    }
}

What you'll see is unexpected: {"name":"Taylor","$observationRegistrar":{}}. Our name property is now _name, there's also an observation registrar instance in the JSON.

Remember, the @Observable macro is quietly rewriting our class so that it can be monitored by SwiftUI, and here that rewriting is leaking – we can see it happening, which might cause all sorts of problems. If you're trying to send a "name" value to a server, it might have no idea what to do with "_name", for example.

To fix this we need to tell Swift exactly how it should encode and decode our data. This is done by nesting an enum inside our class called CodingKeys, and making it have a String raw value and a conformance to the CodingKey protocol. Yes, that's a bit confusing – the enum is called CodingKeys and the protocol is CodingKey, but it does matter.

Inside the enum you need to write one case for each property you want to save, along with a raw value containing the name you want to give it. In our case, that means saying that _name – the underlying storage for our name property – should be written out as the string "name", without an underscore:

@Observable
class User: Codable {
    enum CodingKeys: String, CodingKey {
        case _name = "name"
    }

    var name = "Taylor"
}

And that's it! If you try the code again, you'll see the name property is named correctly, and there's also no more observation registrar in the mix – the result is much cleaner.

This coding key mapping works both ways: when Codable sees name in some JSON, it will automatically be saved in the _name property.

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.8/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.