Encoder & an Actor for the data?

Does anyone have an example of using an Actor as their data type for their custom Encoder?

A TaskGroup to parallelize the encoding?

If so I'd love to see it. I'm not sure it should be even something to try.

1 Like

All Codable related APIs are synchronous, so no you won't be able to use actors or other async/await together with them.

It could be interesting to have an async compatible version of those but we don't have that.

1 Like

Thank you. That's what it looked like, but I was curious if someone who was better than me with both of those things had found a way! Swift 7.0/Codeable as Macro version maybe. :)

Okay what if one did something like the below... would it make any difference at all to try?

(I'm very hand-wavey around starting the TaskGroup because I haven't worked with them yet)

(Also FWIW - I'd rather be using pkl, it looks GREAT!)

struct SimpleCoder {
    let encodedDataStore = SimpleCoderData([:])
    public func encode(_ values: [Encodable]) async throws -> String {
        let encoder = _SimpleEncoder(data:encodedDataStore)
        //<HANDWAVEY>
        let result = TaskGroup {
            for value in values {
               task.append { try value.encode(to: encoder) }
            }
        }
        if result != Error {
            return await encodedDataStore.value()
        }

        //</HANDWAVEY>
    }
    
}


actor SimpleCoderData {
    var storage:[String: String]
    
    init(_ value: [String: String]) {
        self.storage = value
    }
    
    func insert(encodedKey: String, encodedValue:String) {
        storage[encodedKey] = encodedValue
    }
    
    func value() ->  String {
        var lines = storage.map { key, value in
            if key.isEmpty {
                return "\(value)"
            } else {
                return "\(key):\(value)"
            }
        }

        lines.sort()
        return lines.joined(separator: "/")
    }
    
}

struct _SimpleEncoder {
    var data:SimpleCoderData
    var codingPath: [CodingKey] = []
}

extension _SimpleEncoder {
    
    func encodeKey(_ key:CodingKey) -> String {
        (codingPath + [key]).map { $0.stringValue }.joined(separator: ".")
    }
    
    //called from the containers, mostly with just with encode("\(value)", atKey:Key)
    func encode(_ value: String, forKey key:CodingKey) {
        //TODO: is this the best way?
        Task { await data.insert(encodedKey:encodeKey(key), encodedValue:value) }
    }
}

extension _SimpleEncoder:Encoder {
    
    var userInfo: [CodingUserInfoKey : Any] { [:] }
    
    func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key : CodingKey {
        KeyedEncodingContainer(SimpleEncoderKEC<Key>(encoder: self))
    }
    
  //NOTE: Both UEC and SVEC invent a key for their values.
    func unkeyedContainer() -> UnkeyedEncodingContainer {
        SimpleCoderUEC(encoder: self)
    }
    
    func singleValueContainer() -> SingleValueEncodingContainer {
        SimpleCoderSVEC(encoder: self)
    }
    
}

ETA: For future self or others if going down this route check out Package Manager's PlainText encoder because it punts it straight into a stream. For some async needs this would be better than an Actor?