I have a generic Queue data structure which utilizes an array as its list, I am having hard time making the queue conform to encodable and decodable. I have another class which uses the queue which also needs to be Codable but it cannot be coddle unless its member variables are.
I have tried conforming the queue to encodable and decodable, encoding the data and saving it to user defaults but this does not seem to be working, in fact my init(from decoder) function is even stuck in an infinite loop for whatever reason. I could really use some assistance
//My Queue
public struct Queue<T: Codable> {
private var dataSource = [T]()
private var userDefaults = UserDefaults()
public init() {}
public func isEmpty() -> Bool{
return dataSource.isEmpty
}
public mutating func enqueue( element: T){
dataSource.append(element)
}
public mutating func dequeue() -> T?{
return isEmpty() ? nil : dataSource.removeFirst()
}
public func peek() -> T? {
return isEmpty() ? nil : dataSource.first
}
public func getCount() -> Int {
return dataSource.count
}
public func printQueue(){
print(dataSource)
}
}
public enum Error: String, Swift.Error{
case queueNotFound = "Queue Not Found!"
}
extension Queue: Encodable, Decodable {
public func encode(to encoder: Encoder) throws
{
let jsonEncoder = JSONEncoder()
let encodedData = try jsonEncoder.encode(dataSource)
userDefaults.set(encodedData, forKey: "queue")
print(encodedData)
//var container = encoder.container(keyedBy: CodingKey.self)
}
public init(from decoder: Decoder) throws
{
print("intilaizing")
let jsonDecoder = JSONDecoder()
guard let data = userDefaults.data(forKey: "queue"), let _ = try? jsonDecoder.decode(Queue.self, from: data)
else {
throw Error.queueNotFound
}
}
Any class should be able to addd this queue as a data member, when I implement the queue the encode function works I believe but the decoder causes some infinite loop
You are encoding dataSource – which is [T] – but are decoding Queue, this cannot work. Try
public init(from decoder: Decoder) throws
{
print("initializing")
guard let data = userDefaults.data(forKey: "queue") else { throw Error.queueNotFound }
dataSource = try JSONDecoder().decode([T].self, from: data)
}
By the way in your code the decoded value – as well as a potential DecodingError – is unused which makes no sense.
extension Queue {
enum CodingKeys: String, CodingKey {
case dataSource
}
extension Queue: Encodable, Decodable {
public func encode(to encoder: Encoder) throws
{
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(dataSource, forKey: .dataSource)
}
public init(from decoder: Decoder) throws
{
print("initializing Queue:")
let data = try decoder.container(keyedBy: CodingKeys.self)
dataSource = try data.decode(Array.self, forKey: .dataSource)
}
I switched over to using CodingKeys instead, and changed the type to Array.self (Array) which essentially is the same type as the dataSource variable ([T])
Related
[Edited to provide a minimal reproducible example ]
This is the complete struct without non relevant vars and functions
InstrumentSet.swift
import Foundation
struct InstrumentsSet: Identifiable, Codable {
private enum CodingKeys: String, CodingKey {
case name = "setName"
case tracks = "instrumentsConfig"
}
var id: String { name }
var name: String
var tracks: [Track]
}
Track.swift
import Foundation
extension InstrumentsSet {
struct Track: Identifiable, Encodable {
private enum TrackKeys: String, CodingKey {
case id = "trackId"
case effects
}
let id: String
var effects: [Effect]?
}
}
extension InstrumentsSet.Track: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: TrackKeys.self)
id = try container.decode(String.self, forKey: .id)
effects = try container.decodeIfPresent([Effect].self, forKey: .effects)
}
}
Effect.swift
import Foundation
import AudioKit
import SoundpipeAudioKit
extension InstrumentsSet.Track {
enum Effect: Decodable {
private enum EffectKeys: String, CodingKey {
case effectType = "effectName"
case cutoffFrequency
case resonance
}
case lowPassFilter(LowPassFilterEffect)
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: EffectKeys.self)
let effectType = try container.decode(Effect.EffectType.self, forKey: .effectType)
switch effectType {
case .lowPassFilter:
let cutOffFrequency = try container.decode(ValueAndRange.self, forKey: .cutoffFrequency)
let resonance = try container.decode(ValueAndRange.self, forKey: .resonance)
self = .lowPassFilter(LowPassFilterEffect(cutOffFrequency: cutOffFrequency, resonance: resonance))
default:
fatalError("Not implemented!")
}
}
}
}
extension InstrumentsSet.Track.Effect {
enum EffectType: String, Decodable {
case lowPassFilter
}
}
extension InstrumentsSet.Track.Effect: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: EffectKeys.self)
//FIXME: This is the location of the error: Type 'ValueAndRange.Type' cannot conform to 'Encodable'
try container.encode(ValueAndRange.self, forKey: .cutoffFrequency)
}
}
The problem is ValueAndRange.self not not conforming to Encodable
I've followed multiple examples to get to this implementation:
import Foundation
import AudioKit
struct ValueAndRange: Encodable {
private enum ValueRangeKeys: String, CodingKey {
case value
case range
}
static var zero: ValueAndRange { .init(value: 0, range: [0, 0]) }
var value: AUValue
var range: [Double]
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: ValueRangeKeys.self)
try container.encode(value, forKey: .value)
try container.encode(range, forKey: .range)
}
}
extension ValueAndRange: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: ValueRangeKeys.self)
value = try container.decode(AUValue.self, forKey: .value)
range = try container.decode([Double].self, forKey: .range)
}
}
I cannot see why this struct should not conform to Encodable. Maybe any of you got betters eyes (and brains) then I got?
Your encode function is incorrectly trying to encode a type, ValueAndRange.self, rather than a value.
Looking at the init(from:) method I think your encode function should look something like this
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: EffectKeys.self)
switch self {
case .lowPassFilter(let effect):
try container.encode(effect.cutOffFrequency, forKey: .cutoffFrequency)
try container.encode(effect.resonance, forKey: .resonance)
}
}
I didn't include .effectType in this code since I am uncertain of its usage (isn't it always the same hard coded string?).
I want to encode a JSON that could be
{"hw1":{"get_trouble":true},"seq":2,"session_id":1}
or
{"hw2":{"get_trouble":true},"seq":3,"session_id":2}
the class for encoding looks like the following
class Request: Codable {
let sessionId, seq:Int
let content:[String:Content]
enum CodingKeys:String, CodingKey{
case sessionId = "session_id"
case seq
case content
}
init(sessionId:Int, seq:Int, content:[String:Content]) {
self.sessionId = sessionId
self.seq = seq
self.content = content
}
}
class Content:Codable{
let getTrouble = true
enum CodingKeys:String, CodingKey {
case getTrouble = "get_trouble"
}
}
how can I encode the request so that I can get the desired result? Currently, if I do
let request = Request(sessionId: session, seq: seq, content: [type:content])
let jsonData = try! encoder.encode(request)
I get
{"content":{"hw1":{"get_trouble":true}},"seq":2,"session_id":1}
and I don't want "content" inside the JSON. Already looked into
Swift Codable: encode structure with dynamic keys
and couldn't figure out how to apply in my use case
As with almost all custom encoding problems, the tool you need is AnyStringKey (it frustrates me that this isn't in stdlib):
struct AnyStringKey: CodingKey, Hashable, ExpressibleByStringLiteral {
var stringValue: String
init(stringValue: String) { self.stringValue = stringValue }
init(_ stringValue: String) { self.init(stringValue: stringValue) }
var intValue: Int?
init?(intValue: Int) { return nil }
init(stringLiteral value: String) { self.init(value) }
}
This just lets you encode and encode arbitrary keys. With this, the encoder is straightforward:
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: AnyStringKey.self)
for (key, value) in content {
try container.encode(value, forKey: AnyStringKey(key))
}
try container.encode(sessionId, forKey: AnyStringKey("session_id"))
try container.encode(seq, forKey: AnyStringKey("seq"))
}
This assumes you mean to allow multiple key/value pairs in Content. I expect you don't; you're just using a dictionary because you want a better way to encode. If Content has a single key, then you can rewrite it a bit more naturally this way:
// Content only encodes getTrouble; it doesn't encode key
struct Content:Codable{
let key: String
let getTrouble: Bool
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(["get_trouble": getTrouble])
}
}
struct Request: Codable {
// ...
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: AnyStringKey.self)
try container.encode(content, forKey: AnyStringKey(content.key))
try container.encode(sessionId, forKey: AnyStringKey("session_id"))
try container.encode(seq, forKey: AnyStringKey("seq"))
}
}
Now that may still bother you because it pushes part of the Content encoding logic into Request. (OK, maybe it just bothers me.) If you put aside Codable for a moment, you can fix that too.
// Encode Content directly into container
extension KeyedEncodingContainer where K == AnyStringKey {
mutating func encode(_ value: Content) throws {
try encode(["get_trouble": value.getTrouble], forKey: AnyStringKey(value.key))
}
}
struct Request: Codable {
// ...
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: AnyStringKey.self)
// And encode into the container (note no "forKey")
try container.encode(content)
try container.encode(sessionId, forKey: AnyStringKey("session_id"))
try container.encode(seq, forKey: AnyStringKey("seq"))
}
}
I'm using Codable for my WebRequest response which is returning some predefined string or number. So, I'm using Enums for those. But when some unexpected value arrive to Response at that my Codable fails to decode.
Here some code for better understanding.
class WebUser: Codable, Equatable {
static func == (lhs: WebUser, rhs: WebUser) -> Bool {
return lhs.id == rhs.id
}
...
var mobileNumberPrivacy: CommonPrivacyOption?
var emailPrivacy: CommonPrivacyOption?
var dobPrivacy: CommonPrivacyOption?
...
}
enum CommonPrivacyOption: Int, CaseIterable, Codable {
case privacyOnlyMe = 1, privacyPublic, privacyFriends, privacyFriendsOfFriends
//Does not help this optional init function
/*init?(rawValue: Int) {
switch rawValue {
case 1: self = .privacyOnlyMe
case 2: self = .privacyPublic
case 3: self = .privacyFriends
case 4: self = .privacyFriendsOfFriends
default: return nil
}
}*/
}
but sometimes from WebServer I'm getting, 0 for dobPrivacy at that time I'm getting DecodingError.dataCorrupted exception with context Cannot initialize CommonPrivacyOption from invalid Int value 0
As I expect to dobPrivacy nil when I get other values then 1/2/3/4.
EDIT:
let dict1 = [
"id": 2,
"mobileNumberPrivacy": 3,
"emailPrivacy": 4,
"dobPrivacy": 0 // Works perfectly with 1
]
do {
let data1 = try JSONSerialization.data(withJSONObject: dict1, options: .prettyPrinted)
let user1 = try JSONDecoder().decode(WebUser.self, from: data1)
print("User1 created")
}
catch DecodingError.dataCorrupted(let context) {
print(context.codingPath)
print(context.debugDescription)
}
catch {
print(error.localizedDescription)
}
I'm using this same Codable WebUser object for Profile detail, search users and many more.
so may be some times one more key will not present in WebRequest's response.
I recommend writing a property wrapper that handles this problem for you.
Specifically, let's write a property wrapper named NilOnDecodingError that turns any DecodingError into nil.
Here's the declaration of NilOnDecodingError:
#propertyWrapper
public struct NilOnDecodingError<Wrapped> {
public init(wrappedValue: Wrapped?) {
self.wrappedValue = wrappedValue
}
public var wrappedValue: Wrapped?
}
We've defined it to wrap any type, storing an Optional.
Now we can conform it to Decodable when the Wrapped type is Decodable:
extension NilOnDecodingError: Decodable where Wrapped: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
wrappedValue = .some(try container.decode(Wrapped.self))
} catch is DecodingError {
wrappedValue = nil
}
}
}
We probably also want it to be Encodable when the Wrapped type is Encodable:
extension NilOnDecodingError: Encodable where Wrapped: Encodable {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
if let value = wrappedValue {
try container.encode(value)
} else {
try container.encodeNil()
}
}
}
Now we can wrap the appropriate fields of WebUser:
class WebUser: Codable {
let id: String
#NilOnDecodingError
var mobileNumberPrivacy: CommonPrivacyOption?
#NilOnDecodingError
var emailPrivacy: CommonPrivacyOption?
#NilOnDecodingError
var dobPrivacy: CommonPrivacyOption?
}
For testing, we'll want to print the fields of the decoded user:
extension WebUser: CustomStringConvertible {
var description: String {
return """
WebUser(
id: \(id),
mobileNumberPrivacy: \(mobileNumberPrivacy.map { "\($0)" } ?? "nil"),
emailPrivacy: \(emailPrivacy.map { "\($0)" } ?? "nil")),
dobPrivacy: \(dobPrivacy.map { "\($0)" } ?? "nil")))
"""
}
}
Now we can try it out:
let json = """
{
"id": "mrugesh",
"mobileNumberPrivacy": 1,
"emailPrivacy": 2,
"dobPrivacy": 1000
}
"""
let user = try! JSONDecoder().decode(WebUser.self, from: json.data(using: .utf8)!)
print(user)
Output:
WebUser(
id: mrugesh,
mobileNumberPrivacy: privacyOnlyMe,
emailPrivacy: privacyPublic),
dobPrivacy: nil))
You need to create a custom Decodable initializer inside WebUser:
class WebUser: Codable {
var dobPrivacy: CommonPrivacyOption?
// Rest of your properties here.
enum CodingKeys: String, CodingKey {
case dobPrivacy
// Add a case for each property you want to decode here.
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
// Use optional try to decode your enum so that when the
// decode fails because of wrong Int value, it will assign nil.
dobPrivacy = try? container.decode(CommonPrivacyOption.self, forKey: .dobPrivacy)
}
}
Alternatively, you can implement the Decodable initializer inside CommonPrivacyOption and add an additional case unknown like so:
enum CommonPrivacyOption: Int, Codable {
case privacyOnlyMe = 1
case privacyPublic, privacyFriends, privacyFriendsOfFriends
case unknown
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let value = try container.decode(Int.self)
// Try to initialize Self from value, if
// value is not 1, 2, 3, or 4, initialize Self to
// the unknown case.
self = .init(rawValue: value) ?? .unknown
}
}
It looks to me like the compiler selects the wrong init for the enum types, instead of init(rawValue) it uses init(from:) that is the one for decoding (which in a way makes sense)
Here is a solution where we override this behaviour by using a custom init(from) in WebUser that decodes the raw values and then creates an enum item
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let value = try container.decodeIfPresent(CommonPrivacyOption.RawValue.self, forKey: .mobileNumberPrivacy), let mobileNumberPrivacy = CommonPrivacyOption(rawValue: value) {
self.mobileNumberPrivacy = mobileNumberPrivacy
}
if let value = try container.decodeIfPresent(CommonPrivacyOption.RawValue.self, forKey: .emailPrivacy), let emailPrivacy = CommonPrivacyOption(rawValue: value) {
self.emailPrivacy = emailPrivacy
}
if let value = try container.decodeIfPresent(CommonPrivacyOption.RawValue.self, forKey: .dobPrivacy), let dobPrivacy = CommonPrivacyOption(rawValue: value) {
self.dobPrivacy = dobPrivacy
}
}
Below is a small example
extension WebUser: CustomStringConvertible {
var description: String {
"Mobile: \(mobileNumberPrivacy?.rawValue), email: \(emailPrivacy?.rawValue), dob: \(dobPrivacy?.rawValue)"
}
}
let data = """
{
"mobileNumberPrivacy": 1,
"dobPrivacy": 0
}
""".data(using: .utf8)!
do {
let result = try JSONDecoder().decode(WebUser.self, from: data)
print(result)
} catch {
print(error)
}
Mobile: Optional(1), email: nil, dob: nil
Of course if you can change your mind about 0 being translated to nil then I would suggest you extend the enum to support the 0 value
enum CommonPrivacyOption: Int, CaseIterable, Codable {
case none = 0
case privacyOnlyMe = 1, privacyPublic, privacyFriends, privacyFriendsOfFriends
}
Then it should work out of the box and you don't need to write any custom code.
I liked #alobaili's answer since it's simple and provided a good solution. One thing I wanted to improve is to make it more generic, so that any Decodable (and Codable) could do it with less code to write.
extension Decodable where Self: RawRepresentable, RawValue: Decodable {
static func initializedOptionalWith(decoder: Decoder, defaultValue: Self) throws -> Self {
let container = try decoder.singleValueContainer()
let value = try container.decode(RawValue.self)
return .init(rawValue: value) ?? defaultValue
}
}
Usage:
init(from decoder: Decoder) throws {
self = try .initializedOptionalWith(decoder: decoder, defaultValue: .unknown)
}
Thank you Rob Mayoff for a great answer. I would like to add just one thing - if the property is missing, decoding fails even if the property is optional. To prevent this failure, add the following extension:
extension KeyedDecodingContainer {
func decode<T>(_ type: NilOnDecodingError<T>.Type, forKey key: Self.Key) throws -> NilOnDecodingError<T> where T: Decodable {
try decodeIfPresent(type, forKey: key) ?? NilOnDecodingError<T>(wrappedValue: nil)
}
}
The Swift Network Framework includes the Structs IPv4Address and IPv6Address. They are used with NWEndpoints for network connections. The IPv6Address Struct also useful for validating IPv6 address syntax and implementing shortening rules.
How can I make IPv4Address and IPv6Address conform to Codable?
No need to create your own IPv4AddressDecodingError. You can throw a DecodingError using its dataCorruptedError method. Btw there is no need to create a CodingKeys enumeration for a single value:
You can also create a protocol that conforms to RawRepresentable & Codable and constrain RawValue to Codable. This way you can create generic encoder and decoder methods for both ip addresses:
import Network
public protocol RawRepresentableCodableProtocol: RawRepresentable & Codable
where Self.RawValue: Codable { }
public extension RawRepresentableCodableProtocol {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let rawValue = try container.decode(RawValue.self)
guard let object = Self(rawValue: rawValue) else {
throw DecodingError
.dataCorruptedError(in: container, debugDescription: "Invalid rawValue data: \(rawValue)")
}
self = object
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(rawValue)
}
}
Now we can extend RawRepresentableCodableProtocol constraining Self to IPAddress protocol and provide a fallible rawValue data initializer:
public extension RawRepresentableCodableProtocol where Self: IPAddress {
init?(rawValue: Data) {
guard let object = Self(rawValue, nil) else { return nil }
self = object
}
}
extension IPv4Address: RawRepresentableCodableProtocol { }
extension IPv6Address: RawRepresentableCodableProtocol { }
Playground testing:
let ipv4 = IPv4Address("1.2.33.44")! // 1.2.33.44
let dataIPv4 = try JSONEncoder().encode(ipv4) // 10 bytes
let loadedIPv4 = try JSONDecoder().decode(IPv4Address.self, from: dataIPv4) // 1.2.33.44
let ipv6 = IPv6Address("2001:db8::35:44")! // 2001:db8::35:44
let dataIPv6 = try JSONEncoder().encode(ipv6) // 26 bytes
let loadedIPv6 = try JSONDecoder().decode(IPv6Address.self, from: dataIPv6) // 2001:db8::35:44
The trick is to use the IPv4Address.rawValue or IPv6Address.rawValue field to get the IPv4 or IPv6 address in a Data() structure. Then encode the Data.
When decoding, you can use the Data to initialize the address, handling the failable initializer case.
It is also possible to use the IPv6Address.description or IPv6Address.debugDescription to do an encode, but that is not recommended because those descriptions may change format in the future (thx Martin R).
import Foundation
import Network
extension IPv6Address: Codable {
enum CodingKeys: String, CodingKey {
case ipv6Data
}
enum IPv6AddressDecodingError: Error {
case decoding(String)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
let addressData = self.rawValue
try container.encode(addressData, forKey: .ipv6Data)
}
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let addressData = try values.decode(Data.self, forKey: .ipv6Data)
guard let ipv6Address = IPv6Address(addressData) else {
throw IPv6AddressDecodingError.decoding("unable to decode IPv6 address from \(values)")
}
self = ipv6Address
}
}
IPv4Address is essentially identical:
import Foundation
import Network
extension IPv4Address: Codable {
enum CodingKeys: String, CodingKey {
case ipv4Data
}
enum IPv4AddressDecodingError: Error {
case decoding(String)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
let addressData = self.rawValue
try container.encode(addressData, forKey: .ipv4Data)
}
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let addressData = try values.decode(Data.self, forKey: .ipv4Data)
guard let ipv4Address = IPv4Address(addressData) else {
throw IPv4AddressDecodingError.decoding("unable to decode IPv4 address from \(values)")
}
self = ipv4Address
}
}
In SwiftUI beta 5, Apple introduced the #Published annotation. This annotation is currently blocking this class from conforming to the Codable protocols.
How can I conform to these protocols so I can encode and decode this class to JSON? You can ignore the image property for now.
class Meal: ObservableObject, Identifiable, Codable {
enum CodingKeys: String, CodingKey {
case id
case name
case ingredients
case numberOfPeople
}
var id = Globals.generateRandomId()
#Published var name: String = "" { didSet { isInputValid() } }
#Published var image = Image("addImage")
#Published var ingredients: [Ingredient] = [] { didSet { isInputValid() } }
#Published var numberOfPeople: Int = 2
#Published var validInput = false
func isInputValid() {
if name != "" && ingredients.count > 0 {
validInput = true
}
}
}
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)")
}
}
}
A more efficient variant without Mirror
Published+Value.swift
private class PublishedWrapper<T> {
#Published private(set) var value: T
init(_ value: Published<T>) {
_value = value
}
}
extension Published {
var unofficialValue: Value {
PublishedWrapper(self).value
}
}
Published+Codable.swift
extension Published: Decodable where Value: Decodable {
public init(from decoder: Decoder) throws {
self.init(wrappedValue: try .init(from: decoder))
}
}
extension Published: Encodable where Value: Encodable {
public func encode(to encoder: Encoder) throws {
try unofficialValue.encode(to: encoder)
}
}
As for Decodable, we're all answering with the same thing here. Initialize the Published with a decoded Value.
extension Published: Decodable where Value: Decodable {
public init(from decoder: Decoder) throws {
self.init(initialValue: try .init(from: decoder))
}
}
On to Encodable…
Unlike your average property wrapper, Published does not employ wrappedValue. Instead, accessing a Published value triggers a static subscript, which allows it to call objectWillChange on the ObservableObject when set.
Behind the scenes, your Meal.validInput, for example, relies on this code:
Published[
_enclosingInstance: self,
wrapped: \.validInput,
storage: \._validInput
]
_enclosingInstance is necessary for publishing changes, when set, but all it does for get is specify how to access the Published, using this:
_enclosingInstance[keyPath: storageKeyPath]
wrapped is useless for Published.
You always need to supply the subscript with a class instance, but this "_enclosingInstance" does not need to be an ObservableObject.
As such, you can store the Published via another object, and encode its stored value like this:
public extension Published {
/// The stored value of a `Published`.
/// - Note: Only useful when not having access to the enclosing class instance.
var value: Value { Storage(self).value }
private final class Storage {
init(_ published: Published) {
self.published = published
}
var value: Value {
Published[
_enclosingInstance: self,
wrapped: \.never,
storage: \.published
]
}
/// Will never be called, but is necessary to provide a `KeyPath<Value>` for the static subscript.
private var never: Value {
get { fatalError() }
set { fatalError() }
}
/// "`var`" only because the static subscript requires a `WritableKeyPath`.
/// It will never be mutated.
private var published: Published<Value>
}
}
extension Published: Encodable where Value: Encodable {
public func encode(to encoder: Encoder) throws {
try value.encode(to: encoder)
}
}
Alternatively, you could use this for the entirety of the body of Storage. It's just not as clear about documenting how it works.
#Published private(set) var value: Value
init(_ published: Published) {
_value = published
}
Storage will not keep a reference to the ObservableObject, so it's only suitable for capturing values—which is all Encodable needs. Why Apple has not provided us with a built-in solution after all this time, I have no idea.