DEV Community

Diego Lavalle for Swift You and I

Posted on • Originally published at swiftui.diegolavalle.com

Codable observable objects

Complying to the Codable protocol is simple thanks to synthesized initializers and coding keys. Similarly making your class observable using the Combine framework is trivial with ObservableObject. But attempting to merge these two protocols in a single implementation poses a few obstables. Let's find out!

Codable observable objects

A simple class can be made Encodable and Decodable simultaneously by simply declaring it as Codable.

class CodableLandmark: Codable { var site: String = "Unknown site" var visited: Bool = false } 

After this you can easily convert objects to JSON format and back.

import Foundation class CodableLandmark: Codable {  // Encode this instance as JSON data var asJsonData: Data? { try? JSONEncoder().encode(self) } // Create an instance from JSON data static func fromJsonData(_ json: Data) -> Self? { guard let decoded = try? JSONDecoder().decode(Self.self, from: json) else { return nil } return decoded } } 

An identical class could alternatively serve as a model for some SwiftUI view simply by adhering to ObservableObject. Annotating properties with the @Published wrapper will ensure that notifications are generated when these values change.

import Combine class ObservableLandmark: ObservableObject { @Published var site: String = "Unknown site" @Published var visited: Bool = false } 

Initially just adding the ObservableObject to our codable class' protocol list produces no error. But when we try to mark the first variable as published we encounter error messages Type 'LandmarkModel' does not conform to protocol 'Decodable' and Type 'LandmarkModel' does not conform to protocol 'Encodable' error.

class LandmarkModel: Codable, ObservableObject { @Published // Error var site: String = "Unknown site" @Published // Error var visited: Bool = false } 

The solution is to simply implement some of the Codable requirements manually rather than have them synthesized. Namely the initializer (for decoding) and the encode function for encoding. This forces us to additionally declare our coding keys explicitly.

class LandmarkModel: Codable, ObservableObject {  // MARK: - Codable enum CodingKeys: String, CodingKey { case site case visited } // MARK: - Codable required init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) site = try values.decode(String.self, forKey: .site) visited = try values.decode(Bool.self, forKey: .visited) } // MARK: - Encodable func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(site, forKey: .site) try container.encode(visited, forKey: .visited) } } 

This approach of tightly coupling the codable and observable models can be useful for simple projects as it avoids a lot of boilerplate code.

Check out the associated Working Example to see this technique in action.

FEATURED EXAMPLE: Real-Time JSON - Observe the codable object


Originally published at Swift You and I.

Top comments (0)