53 lines
1.8 KiB
Swift
53 lines
1.8 KiB
Swift
import FoundationEssentials
|
|
|
|
@dynamicMemberLookup
|
|
public struct Nutriments: Codable, Sendable {
|
|
private var storage: [String: Double]
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: AnyCodingKey.self)
|
|
var dict = [String: Double]()
|
|
for key in container.allKeys {
|
|
if let val = try? container.decode(Double.self, forKey: key) {
|
|
dict[key.stringValue] = val
|
|
} else if let valStr = try? container.decode(
|
|
String.self, forKey: key), let val = Double(valStr)
|
|
{
|
|
dict[key.stringValue] = val
|
|
}
|
|
}
|
|
self.storage = dict
|
|
}
|
|
|
|
public func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: AnyCodingKey.self)
|
|
for (key, value) in storage {
|
|
try container.encode(value, forKey: AnyCodingKey(stringValue: key))
|
|
}
|
|
}
|
|
|
|
/// Access any nutrient dynamically (e.g. `nutriments.energyKcal`)
|
|
public subscript(dynamicMember member: String) -> Double? {
|
|
// Convert camelCase "energyKcal" to snake_case "energy-kcal" or "energy_kcal" logic if needed
|
|
// For V2, OFF often returns "energy-kcal_100g"
|
|
let snake = member.camelCaseToSnakeCase()
|
|
return storage[snake] ?? storage["\(snake)_100g"]
|
|
?? storage["\(snake)_value"]
|
|
}
|
|
|
|
// Specific standard getters
|
|
public var energyKcal: Double? { self.storage["energy-kcal_100g"] }
|
|
public var carbohydrates: Double? { self.storage["carbohydrates_100g"] }
|
|
public var fat: Double? { self.storage["fat_100g"] }
|
|
public var proteins: Double? { self.storage["proteins_100g"] }
|
|
public var salt: Double? { self.storage["salt_100g"] }
|
|
}
|
|
|
|
// Helper for String extension used above
|
|
struct AnyCodingKey: CodingKey {
|
|
var stringValue: String
|
|
var intValue: Int?
|
|
init(stringValue: String) { self.stringValue = stringValue }
|
|
init?(intValue: Int) { return nil }
|
|
}
|