SwiftyLab / MetaCodable

Supercharge Swift's Codable implementations with macros meta-programming.
https://swiftpackageindex.com/SwiftyLab/MetaCodable/main/documentation/metacodable
MIT License
604 stars 22 forks source link

Custom parsing logic inside decoding method #85

Closed ljd1bg closed 4 months ago

ljd1bg commented 4 months ago

I'm trying to add custom parsing logic to init(from:) method, and my nested model depends on a property "above" it. You can see this in the response:

{
    "id": "12345",
    "time_zone": "Europe/Berlin", // need to use this when parsing `regular_hours` so that I can figure out proper dates/times...
    "opening_times": {
        "twentyfourseven": false,
        "regular_hours": [
            {
                "weekday": 6,
                "period_begin": "08:00",
                "period_end": "02:00"
            },
            {
                "weekday": 5,
                "period_begin": "08:00",
                "period_end": "02:00"
            }
        ]
    }
}

And I want to achieve something like this:

public struct LocationDetails: Decodable {
    public let id: String
    public let timeZone: TimeZone
    private let regularHours: [RegularHours]

    private enum CodingKeys: String, CodingKey {
        case id
        case timeZone = "time_zone"
        case regularHours = "regular_hours"
        case openingTimes = "opening_times"
    }

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

        self.id = try container.decode(String.self, forKey: CodingKeys.id)
        self.timeZone = try ValueCoder<TimeZone>().decode(from: container, forKey: CodingKeys.timeZone)

        if let openingTimes_container = try? container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.openingTimes) {
            do {
                let hours = try openingTimes_container.decode([RegularHoursDto].self, forKey: CodingKeys.regularHours)

                // Parsing logic
                let calendar = Calendar.iso8601
                let today = Date()
                let todayIndex = today.weekday(from: calendar)
                let todayComponents = calendar.dateComponents([.year, .month, .day], from: today)

                let tz = timeZone

                self.regularHours = hours.map {
                    // Parsing logic...
                    let beginDateComponents = DateComponents(timeZone: tz, ...)
                    let endDateComponents = DateComponents(timeZone: tz, ...)

                    return RegularHours(beginDate: calendar.date(from: beginDateComponents)!,
                                            endDate: calendar.date(from: endDateComponents)!,
                                            timezone: tz)
                }
            } catch {
                self.regularHours = []
            }
        } else {
            self.regularHours = []
        }

    }
}

public struct RegularHours {
    public let beginDate: Date
    public let endDate: Date
    public let timezone: TimeZone // Not in this object, but one level above it
}

public struct RegularHoursDto: Decodable {
    public let weekday: Int
    public let periodBegin: String
    public let periodEnd: String

    private enum CodingKeys: String, CodingKey {
        case weekday
        case periodBegin = "period_begin"
        case periodEnd = "period_end"
    }

    public init(from decoder: any Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.weekday = try container.decode(Int.self, forKey: CodingKeys.weekday)
        self.periodBegin = try container.decode(String.self, forKey: CodingKeys.periodBegin)
        self.periodEnd = try container.decode(String.self, forKey: CodingKeys.periodEnd)
    }
}

Is there a way to do something like this with pre-defined macros?

Thanks!

soumyamahunt commented 4 months ago

From what you are describing I think it can be achievable with HelperCoder functionality, have you looked into this?

I think places like StackOverflow or Swift Forums etc. will. be better for such questions as you will get wider audience, and somebody might have already achieved what you are expecting.