240 lines
6.2 KiB
Swift
240 lines
6.2 KiB
Swift
import Foundation
|
|
import Units
|
|
|
|
@dynamicMemberLookup
|
|
public struct Nutriments: Codable, ObjectDebugger {
|
|
private var nutrients: [String: Nutrient] = [:] // Key is accessName ( - replaced with _ )
|
|
|
|
public init() {}
|
|
|
|
public subscript(dynamicMember member: String) -> Nutrient? {
|
|
nutrients[member]
|
|
}
|
|
|
|
// MARK: - Decoding
|
|
public init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: AnyCodingKey.self)
|
|
for key in container.allKeys {
|
|
let keyStr = key.stringValue
|
|
let parts = keyStr.components(separatedBy: "_")
|
|
|
|
var nutrientName = "" // Original with -
|
|
var field = ""
|
|
var isPrepared = false
|
|
|
|
if let preparedIdx = parts.firstIndex(of: "prepared") {
|
|
isPrepared = true
|
|
nutrientName = parts[0..<preparedIdx].joined(separator: "_")
|
|
let afterParts = Array(parts[(preparedIdx + 1)...])
|
|
field = afterParts.joined(separator: "_")
|
|
} else {
|
|
nutrientName = parts.dropLast().joined(separator: "_")
|
|
field = parts.last ?? ""
|
|
}
|
|
|
|
let accessName = nutrientName.replacingOccurrences(
|
|
of: "-", with: "_")
|
|
if nutrients[accessName] == nil {
|
|
nutrients[accessName] = Nutrient(name: nutrientName)
|
|
}
|
|
|
|
let valDouble: Double? = try? container.decode(
|
|
Double.self, forKey: key)
|
|
let valString: String? = try? container.decode(
|
|
String.self, forKey: key)
|
|
|
|
let currentNutrient = nutrients[accessName]!
|
|
|
|
switch field {
|
|
case "":
|
|
if isPrepared {
|
|
currentNutrient.preparedValue = valDouble
|
|
} else {
|
|
currentNutrient.value = valDouble
|
|
}
|
|
case "value":
|
|
if isPrepared {
|
|
currentNutrient.preparedValueComputed = valDouble
|
|
} else {
|
|
currentNutrient.valueComputed = valDouble
|
|
}
|
|
case "100g":
|
|
if isPrepared {
|
|
currentNutrient.preparedPer100g = valDouble
|
|
} else {
|
|
currentNutrient.per100g = valDouble
|
|
}
|
|
case "serving":
|
|
if isPrepared {
|
|
currentNutrient.preparedPerServing = valDouble
|
|
} else {
|
|
currentNutrient.perServing = valDouble
|
|
}
|
|
case "unit":
|
|
if isPrepared {
|
|
currentNutrient.preparedUnit = valString
|
|
} else {
|
|
currentNutrient.unit = valString
|
|
}
|
|
default:
|
|
// Skip unknown fields
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Encoding
|
|
public func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: AnyCodingKey.self)
|
|
for (_, nutrient) in nutrients {
|
|
// Base value
|
|
if let v = nutrient.value {
|
|
let key = AnyCodingKey(stringValue: nutrient.name)
|
|
try container.encode(v, forKey: key)
|
|
}
|
|
|
|
// _value
|
|
if let v = nutrient.valueComputed {
|
|
let key = AnyCodingKey(stringValue: "\(nutrient.name)_value")
|
|
try container.encode(v, forKey: key)
|
|
}
|
|
|
|
// _unit
|
|
if let u = nutrient.unit {
|
|
let key = AnyCodingKey(stringValue: "\(nutrient.name)_unit")
|
|
try container.encode(u, forKey: key)
|
|
}
|
|
|
|
// _100g
|
|
if let v = nutrient.per100g {
|
|
let key = AnyCodingKey(stringValue: "\(nutrient.name)_100g")
|
|
try container.encode(v, forKey: key)
|
|
}
|
|
|
|
// _serving
|
|
if let v = nutrient.perServing {
|
|
let key = AnyCodingKey(stringValue: "\(nutrient.name)_serving")
|
|
try container.encode(v, forKey: key)
|
|
}
|
|
|
|
// _prepared
|
|
if let v = nutrient.preparedValue {
|
|
let key = AnyCodingKey(stringValue: "\(nutrient.name)_prepared")
|
|
try container.encode(v, forKey: key)
|
|
}
|
|
|
|
// _prepared_value
|
|
if let v = nutrient.preparedValueComputed {
|
|
let key = AnyCodingKey(
|
|
stringValue: "\(nutrient.name)_prepared_value")
|
|
try container.encode(v, forKey: key)
|
|
}
|
|
|
|
// _prepared_unit
|
|
if let u = nutrient.preparedUnit {
|
|
let key = AnyCodingKey(
|
|
stringValue: "\(nutrient.name)_prepared_unit")
|
|
try container.encode(u, forKey: key)
|
|
}
|
|
|
|
// _prepared_100g
|
|
if let v = nutrient.preparedPer100g {
|
|
let key = AnyCodingKey(
|
|
stringValue: "\(nutrient.name)_prepared_100g")
|
|
try container.encode(v, forKey: key)
|
|
}
|
|
|
|
// _prepared_serving
|
|
if let v = nutrient.preparedPerServing {
|
|
let key = AnyCodingKey(
|
|
stringValue: "\(nutrient.name)_prepared_serving")
|
|
try container.encode(v, forKey: key)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Nutrient Struct with Units Integration
|
|
public class Nutrient { // Class for mutability in decoding
|
|
public let name: String // Original name with -
|
|
|
|
public var value: Double?
|
|
public var valueComputed: Double?
|
|
public var per100g: Double?
|
|
public var perServing: Double?
|
|
public var unit: String?
|
|
|
|
public var preparedValue: Double?
|
|
public var preparedValueComputed: Double?
|
|
public var preparedPer100g: Double?
|
|
public var preparedPerServing: Double?
|
|
public var preparedUnit: String?
|
|
|
|
init(name: String) {
|
|
self.name = name
|
|
}
|
|
|
|
// MARK: - Integration with Units.swift
|
|
// Computed UnitValue for per100g (fallback to value or valueComputed if per100g is nil)
|
|
public var per100gUnitValue: UnitValue<Double>? {
|
|
guard let rawValue = per100g ?? value ?? valueComputed,
|
|
let unitString = unit,
|
|
let unitEnum = Unit(rawValue: unitString)
|
|
else {
|
|
return nil
|
|
}
|
|
return UnitValue(value: rawValue, unit: unitEnum)
|
|
}
|
|
|
|
// Computed UnitValue for perServing
|
|
public var perServingUnitValue: UnitValue<Double>? {
|
|
guard let rawValue = perServing,
|
|
let unitString = unit,
|
|
let unitEnum = Unit(rawValue: unitString)
|
|
else {
|
|
return nil
|
|
}
|
|
return UnitValue(value: rawValue, unit: unitEnum)
|
|
}
|
|
|
|
// Computed UnitValue for preparedPer100g (fallback similar)
|
|
public var preparedPer100gUnitValue: UnitValue<Double>? {
|
|
guard
|
|
let rawValue = preparedPer100g ?? preparedValue
|
|
?? preparedValueComputed,
|
|
let unitString = preparedUnit ?? unit, // Fallback to main unit if preparedUnit nil
|
|
let unitEnum = Unit(rawValue: unitString)
|
|
else {
|
|
return nil
|
|
}
|
|
return UnitValue(value: rawValue, unit: unitEnum)
|
|
}
|
|
|
|
// Computed UnitValue for preparedPerServing
|
|
public var preparedPerServingUnitValue: UnitValue<Double>? {
|
|
guard let rawValue = preparedPerServing,
|
|
let unitString = preparedUnit ?? unit,
|
|
let unitEnum = Unit(rawValue: unitString)
|
|
else {
|
|
return nil
|
|
}
|
|
return UnitValue(value: rawValue, unit: unitEnum)
|
|
}
|
|
}
|
|
|
|
// MARK: - Helper for Dynamic Coding Keys
|
|
struct AnyCodingKey: CodingKey, Hashable {
|
|
var stringValue: String
|
|
var intValue: Int?
|
|
|
|
init(stringValue: String) {
|
|
self.stringValue = stringValue
|
|
self.intValue = nil
|
|
}
|
|
|
|
init(intValue: Int) {
|
|
self.intValue = intValue
|
|
self.stringValue = String(intValue)
|
|
}
|
|
}
|