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.
SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure and A/B test your entire paywall UI without any code changes or app updates.
Sponsor Hacking with Swift and reach the world's largest Swift community!
Link copied to your pasteboard.