How to conform an ObservableObject to the Codable protocols?

Confused Vorlon has the right idea! I'm trying to work with them to make something robust. I believe this should be all you need, building from Xcode 12.

Published's storage, and Published.Publisher's subject, are private API, so mirroring is the best option to dig in where needed:

import struct Combine.Published

extension Published: Encodable where Value: Encodable {
  public func encode(to encoder: Encoder) throws {
    guard
      let storageValue =
        Mirror(reflecting: self).descendant("storage")
        .map(Mirror.init)?.children.first?.value,
      let value =
        storageValue as? Value
        ??
        (storageValue as? Publisher).map(Mirror.init)?
        .descendant("subject", "currentValue")
        as? Value
    else { throw EncodingError.invalidValue(self, codingPath: encoder.codingPath) }
    
    try value.encode(to: encoder)
  }
}

extension Published: Decodable where Value: Decodable {
  public init(from decoder: Decoder) throws {
    self.init(
      initialValue: try .init(from: decoder)
    )
  }
}
extension EncodingError {
  /// `invalidValue` without having to pass a `Context` as an argument.
  static func invalidValue(
    _ value: Any,
    codingPath: [CodingKey],
    debugDescription: String = .init()
  ) -> Self {
    .invalidValue(
      value,
      .init(
        codingPath: codingPath,
        debugDescription: debugDescription
      )
    )
  }
}

Add the init() and encode() methods to your class:

required init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)

    id = try values.decode(Int.self, forKey: .id)
    name = try values.decode(String.self, forKey: .name)
    ingredients = try values.decode([Ingredient].self, forKey: .ingredients)
    numberOfPeople = try values.decode(Int.self, forKey: .numberOfPeople)
}

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)
    try container.encode(ingredients, forKey: .ingredients)
    try container.encode(numberOfPeople, forKey: .numberOfPeople)
}

After much hacking around, I managed to add Codable directly to @Published

Note I had to update this for iOS14. This illustrates the danger of digging around in undocumented types...

Just add the code below in a file and your @Published variables will be automatically Codable (provided they are based on a Codable type)

more info here https://blog.hobbyistsoftware.com/2020/01/adding-codeable-to-published/

code here:

import Foundation
import SwiftUI

extension Published:Decodable where Value:Decodable {
    public init(from decoder: Decoder) throws {
        let decoded = try Value(from:decoder)
        self = Published(initialValue:decoded)
    }
}

 extension Published:Encodable where Value:Decodable {

    private var valueChild:Any? {
        let mirror = Mirror(reflecting: self)
        if let valueChild = mirror.descendant("value") {
            return valueChild
        }
        
        //iOS 14 does things differently...
        if let valueChild = mirror.descendant("storage","value") {
            return valueChild
        }
        
        //iOS 14 does this too...
        if let valueChild = mirror.descendant("storage","publisher","subject","currentValue") {
            return valueChild
        }

        return nil
    }
   
    public func encode(to encoder: Encoder) throws {
        
        guard let valueChild = valueChild else {
            fatalError("Mirror Mirror on the wall - why no value y'all : \(self)")
        }
        
        if let value = valueChild.value as? Encodable {
            do {
                try value.encode(to: encoder)
                return
            } catch let error {
                assertionFailure("Failed encoding: \(self) - \(error)")
            }
        }
        else {
            assertionFailure("Decodable Value not decodable. Odd \(self)")
        }
    }
}