struct HousePlant:Codable { let commonName:String let whereAcquired:String? let dateAcquired:Date var currentPropCount:UInt8 let dateOfDeath:Date? } let serializedHousePlant = "commonName:spider plant/whereAcquired:Trader Joe's/dateAcquired: 2024-03-12T16:12:07Z/currentPropCount:8" let decoder = try HousePlantDecoder(serializedHousePlant) let myHousePlant = try HousePlant(from: decoder) print(myHousePlant) final class SimpleCoderData { var storage:Value init(_ value: Value) { self.storage = value } } enum HousePlantDecoderError:Error { case notAKeyValuePair case unknown(String) } struct HousePlantDecoder:Decoder { //let originalData:String var data:SimpleCoderData> var codingPath: [any CodingKey] = [] var userInfo: [CodingUserInfoKey : Any] = [:] func retrieveValue(forKey codingKey:CodingKey) throws -> String { guard let value = data.storage[codingKey.stringValue] else { throw DecodingError.keyNotFound(codingKey, DecodingError.Context(codingPath: codingPath, debugDescription: "Didn't find key in decoder data.")) } return value } func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key : CodingKey { KeyedDecodingContainer(HousePlantDecoderKDC(decoder: self)) } func unkeyedContainer() throws -> any UnkeyedDecodingContainer { fatalError() } func singleValueContainer() throws -> any SingleValueDecodingContainer { fatalError() } } extension HousePlantDecoder { static func parse(_ inputString:String) throws -> Dictionary { let fullKeyValueList = inputString.split(separator: "/") var result:Dictionary = [:] return try fullKeyValueList.reduce(into: result){ result, item in if let firstColonIndex = item.firstIndex(of: ":") { let key = item .prefix(upTo: firstColonIndex) .trimmingCharacters(in: .whitespacesAndNewlines) let value = item .suffix(from: item.index(firstColonIndex, offsetBy: 1)) .trimmingCharacters(in: .whitespacesAndNewlines) result[String(key)] = String(value) } else { throw HousePlantDecoderError.notAKeyValuePair } } } } extension HousePlantDecoder { init(_ inputString:String) throws { self.data = SimpleCoderData(try HousePlantDecoder.parse(inputString)) } } extension HousePlantDecoder { func _decodeDate(from value:String) throws -> Date { let format = if value.contains(":") { Date.ISO8601FormatStyle.iso8601 } else { Date.ISO8601FormatStyle.iso8601 .year().month().day() } guard let date = try? format.parse(value) else { throw DecodingError.dataCorrupted( .init(codingPath: [], debugDescription: "String not in expected \(format.parseStrategy) format.") ) } return date } func _decodeLossless(_ value:String, ofType: L.Type) throws -> L { guard let result = L.init(value) else { throw DecodingError.typeMismatch(L.self, DecodingError.Context(codingPath: codingPath, debugDescription: "Attempt to decode \(value) as \(L.self) failed.")) } return result } } struct HousePlantDecoderKDC:KeyedDecodingContainerProtocol { let decoder:HousePlantDecoder var codingPath: [any CodingKey] { decoder.codingPath } var allKeys: [Key] { decoder.data.storage.keys.compactMap({ Key(stringValue: $0) }) } func contains(_ key: Key) -> Bool { decoder.data.storage.keys.contains(key.stringValue) } func decodeNil(forKey key: Key) throws -> Bool { //return true if the value should be .none //false if .some, and an attempt should be made to decode the //wrapped value if decoder.data.storage.keys.contains(key.stringValue) { let value = decoder.data.storage[key.stringValue] return value == "NULL" || value == "nil" } else { return true } } func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { fatalError() } func decode(_ type: String.Type, forKey key: Key) throws -> String { try decoder.retrieveValue(forKey: key) } func decode(_ type: Double.Type, forKey key: Key) throws -> Double { fatalError() } func decode(_ type: Float.Type, forKey key: Key) throws -> Float { fatalError() } func decode(_ type: Int.Type, forKey key: Key) throws -> Int { let value = try decoder.retrieveValue(forKey: key) if let int = Int(value) { return int } throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "Could not make \(type) from \(value)")) } func decode(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable { switch type { case is Bool.Type: return try decode(Bool.self, forKey: key) as! T case is String.Type: return try decode(String.self, forKey: key) as! T case is Double.Type: return try decode(Double.self, forKey: key) as! T case is Float.Type: return try decode(Float.self, forKey: key) as! T case is Int.Type: return try decode(Int.self, forKey: key) as! T case is LosslessStringConvertible.Type : let value = try decoder.retrieveValue(forKey: key) if let convertible = T.self as? LosslessStringConvertible.Type { return try decoder._decodeLossless(value, ofType:convertible) as! T } else { throw DecodingError.dataCorruptedError( forKey: key, in: self, debugDescription: "Thought \(value) could be converted to \(type.self) as a LosslessStringConvertible. Something went wrong.") } case is Date.Type: let value = try decoder.retrieveValue(forKey: key) print(value) return try decoder._decodeDate(from: value) as! T default: let value = try decoder.retrieveValue(forKey: key) fatalError("in the default, unhandled type \(type). \(value)") //let tmpDecoder = try HousePlantDecoder(value) //return try type.init(from:tmpDecoder) } } func decodeIfPresent(_ type: T.Type, forKey key: Key) throws -> T? where T: Decodable { guard contains(key) else { return nil } return try decode(type, forKey: key) } func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey : CodingKey { fatalError() } func nestedUnkeyedContainer(forKey key: Key) throws -> any UnkeyedDecodingContainer { fatalError() } func superDecoder(forKey key: Key) throws -> any Decoder { fatalError() } func superDecoder() throws -> any Decoder { decoder } }