Should work fine
This commit is contained in:
@@ -1,12 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
extension String {
|
|
||||||
public func camelCaseToSnakeCase() -> String {
|
|
||||||
let pattern = "([a-z0-9])([A-Z])"
|
|
||||||
let regex = try! NSRegularExpression(pattern: pattern, options: [])
|
|
||||||
let range = NSRange(location: 0, length: self.count)
|
|
||||||
return regex.stringByReplacingMatches(
|
|
||||||
in: self, options: [], range: range, withTemplate: "$1_$2"
|
|
||||||
).lowercased()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +1,146 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
import Units
|
||||||
|
|
||||||
@dynamicMemberLookup
|
@dynamicMemberLookup
|
||||||
public struct Nutriments: Codable, Sendable {
|
public struct Nutriments: Codable, Sendable {
|
||||||
private var storage: [String: Double]
|
// We separate values (Doubles) and units (Strings) for efficient, thread-safe storage
|
||||||
|
private let values: [String: Double]
|
||||||
|
private let units: [String: String]
|
||||||
|
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
let container = try decoder.container(keyedBy: AnyCodingKey.self)
|
let container = try decoder.container(keyedBy: AnyCodingKey.self)
|
||||||
var dict = [String: Double]()
|
var v = [String: Double]()
|
||||||
|
var u = [String: String]()
|
||||||
|
|
||||||
for key in container.allKeys {
|
for key in container.allKeys {
|
||||||
if let val = try? container.decode(Double.self, forKey: key) {
|
let keyStr = key.stringValue
|
||||||
dict[key.stringValue] = val
|
|
||||||
} else if let valStr = try? container.decode(
|
// Try decoding as Double first (most common for _100g, _serving)
|
||||||
String.self, forKey: key), let val = Double(valStr)
|
if let doubleVal = try? container.decode(Double.self, forKey: key) {
|
||||||
|
v[keyStr] = doubleVal
|
||||||
|
}
|
||||||
|
// Try decoding as String (could be a unit OR a number in string format)
|
||||||
|
else if let stringVal = try? container.decode(
|
||||||
|
String.self, forKey: key)
|
||||||
{
|
{
|
||||||
dict[key.stringValue] = val
|
if let doubleFromStr = Double(stringVal) {
|
||||||
|
v[keyStr] = doubleFromStr
|
||||||
|
} else {
|
||||||
|
u[keyStr] = stringVal // It's likely a unit like "kcal", "g", "kj"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.storage = dict
|
}
|
||||||
|
self.values = v
|
||||||
|
self.units = u
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(to encoder: Encoder) throws {
|
public func encode(to encoder: Encoder) throws {
|
||||||
var container = encoder.container(keyedBy: AnyCodingKey.self)
|
var container = encoder.container(keyedBy: AnyCodingKey.self)
|
||||||
for (key, value) in storage {
|
for (key, val) in values {
|
||||||
try container.encode(value, forKey: AnyCodingKey(stringValue: key))
|
try container.encode(val, forKey: AnyCodingKey(stringValue: key))
|
||||||
|
}
|
||||||
|
for (key, unit) in units {
|
||||||
|
try container.encode(unit, forKey: AnyCodingKey(stringValue: key))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access any nutrient dynamically (e.g. `nutriments.energyKcal`)
|
/// Dynamic lookup: converts `nutriments.energyKcal` -> `Nutrient(name: "energy-kcal")`
|
||||||
public subscript(dynamicMember member: String) -> Double? {
|
public subscript(dynamicMember member: String) -> Nutrient {
|
||||||
// Convert camelCase "energyKcal" to snake_case "energy-kcal" or "energy_kcal" logic if needed
|
let kebabName = member.camelCaseToKebabCase()
|
||||||
// For V2, OFF often returns "energy-kcal_100g"
|
return Nutrient(name: kebabName, values: values, units: units)
|
||||||
let snake = member.camelCaseToSnakeCase()
|
}
|
||||||
return storage[snake] ?? storage["\(snake)_100g"]
|
|
||||||
?? storage["\(snake)_value"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specific standard getters
|
// MARK: - Nutrient View
|
||||||
public var energyKcal: Double? { self.storage["energy-kcal_100g"] }
|
public struct Nutrient: Sendable {
|
||||||
public var carbohydrates: Double? { self.storage["carbohydrates_100g"] }
|
public let name: String
|
||||||
public var fat: Double? { self.storage["fat_100g"] }
|
private let values: [String: Double]
|
||||||
public var proteins: Double? { self.storage["proteins_100g"] }
|
private let units: [String: String]
|
||||||
public var salt: Double? { self.storage["salt_100g"] }
|
|
||||||
|
internal init(
|
||||||
|
name: String, values: [String: Double], units: [String: String]
|
||||||
|
) {
|
||||||
|
self.name = name
|
||||||
|
self.values = values
|
||||||
|
self.units = units
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper for String extension used above
|
// MARK: - Raw Properties (Mapped to V2 JSON keys)
|
||||||
struct AnyCodingKey: CodingKey {
|
public var per100g: Double? { values["\(name)_100g"] }
|
||||||
|
public var perServing: Double? { values["\(name)_serving"] }
|
||||||
|
public var value: Double? { values["\(name)_value"] ?? values[name] }
|
||||||
|
public var unit: String? { units["\(name)_unit"] }
|
||||||
|
|
||||||
|
// Computed / Legacy
|
||||||
|
public var valueComputed: Double? { values["\(name)_value"] }
|
||||||
|
|
||||||
|
// Prepared
|
||||||
|
public var preparedPer100g: Double? { values["\(name)_prepared_100g"] }
|
||||||
|
public var preparedPerServing: Double? {
|
||||||
|
values["\(name)_prepared_serving"]
|
||||||
|
}
|
||||||
|
public var preparedValue: Double? {
|
||||||
|
values["\(name)_prepared_value"] ?? values["\(name)_prepared"]
|
||||||
|
}
|
||||||
|
public var preparedUnit: String? { units["\(name)_prepared_unit"] }
|
||||||
|
public var preparedValueComputed: Double? {
|
||||||
|
values["\(name)_prepared_value"]
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - UnitValue Helpers (Restored)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
public var preparedPer100gUnitValue: UnitValue<Double>? {
|
||||||
|
guard
|
||||||
|
let rawValue = preparedPer100g ?? preparedValue
|
||||||
|
?? preparedValueComputed,
|
||||||
|
let unitString = preparedUnit ?? unit, // Fallback to main unit
|
||||||
|
let unitEnum = Unit(rawValue: unitString)
|
||||||
|
else { return nil }
|
||||||
|
return UnitValue(value: rawValue, unit: unitEnum)
|
||||||
|
}
|
||||||
|
|
||||||
|
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: - Helpers
|
||||||
|
|
||||||
|
private struct AnyCodingKey: CodingKey {
|
||||||
var stringValue: String
|
var stringValue: String
|
||||||
var intValue: Int?
|
var intValue: Int?
|
||||||
init(stringValue: String) { self.stringValue = stringValue }
|
init(stringValue: String) { self.stringValue = stringValue }
|
||||||
init?(intValue: Int) { return nil }
|
init?(intValue: Int) { return nil }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension String {
|
||||||
|
// Converts "energyKcal" -> "energy-kcal"
|
||||||
|
fileprivate func camelCaseToKebabCase() -> String {
|
||||||
|
let pattern = "([a-z0-9])([A-Z])"
|
||||||
|
let regex = try! NSRegularExpression(pattern: pattern, options: [])
|
||||||
|
let range = NSRange(location: 0, length: self.count)
|
||||||
|
return regex.stringByReplacingMatches(
|
||||||
|
in: self, options: [], range: range, withTemplate: "$1-$2"
|
||||||
|
).lowercased()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
26
Sources/OpenFoodFactsSDK/Schemas/NutriscoreData.swift
Normal file
26
Sources/OpenFoodFactsSDK/Schemas/NutriscoreData.swift
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
public struct NutriscoreData: Sendable, Codable {
|
||||||
|
|
||||||
|
public let saturatedFatRatio: Float?
|
||||||
|
public let saturatedFatRatioPoints: Int?
|
||||||
|
public let saturatedFatRatioValue: Float?
|
||||||
|
|
||||||
|
public let isBeverage: Int?
|
||||||
|
public let isCheese: Int?
|
||||||
|
public let isWater: Int?
|
||||||
|
public let isFat: Int?
|
||||||
|
public let energy: Int?
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
public enum CodingKeys: String, CodingKey {
|
||||||
|
case saturatedFatRatio = "saturated_fat_ratio"
|
||||||
|
case saturatedFatRatioPoints = "saturated_fat_ratio_points"
|
||||||
|
case saturatedFatRatioValue = "saturated_fat_ratio_value"
|
||||||
|
case isBeverage = "is_beverage"
|
||||||
|
case isCheese = "is_cheese"
|
||||||
|
case isWater = "is_water"
|
||||||
|
case isFat = "is_fat"
|
||||||
|
case energy
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,98 +1,236 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
import Units
|
||||||
|
|
||||||
public struct Product: Codable, Sendable, Identifiable {
|
public struct Product: Codable, Sendable, Identifiable {
|
||||||
public let code: String?
|
public let code: String?
|
||||||
public let productName: String?
|
public let productName: String?
|
||||||
|
public let genericName: String?
|
||||||
public let brands: String?
|
public let brands: String?
|
||||||
|
public let brandsTags: [String]?
|
||||||
public let quantity: String?
|
public let quantity: String?
|
||||||
|
|
||||||
|
// MARK: - Images
|
||||||
public let imageFrontUrl: String?
|
public let imageFrontUrl: String?
|
||||||
public let imageSmallUrl: String?
|
public let imageSmallUrl: String?
|
||||||
public let ingredientsText: String?
|
public let imageUrl: String?
|
||||||
|
public let imageNutritionSmallUrl: String?
|
||||||
|
public let imageNutritionThumbUrl: String?
|
||||||
|
public let imageNutritionUrl: String?
|
||||||
|
public let imageIngredientsSmallUrl: String?
|
||||||
|
public let imageIngredientsThumbUrl: String?
|
||||||
|
public let imageIngredientsUrl: String?
|
||||||
|
|
||||||
// Grades
|
// MARK: - Quantities
|
||||||
|
public let productQuantity: Float?
|
||||||
|
public let productQuantityUnit: String?
|
||||||
|
public let servingQuantity: String?
|
||||||
|
public let servingQuantityUnit: String?
|
||||||
|
public let servingSize: String?
|
||||||
|
|
||||||
|
// MARK: - Grades & Scores
|
||||||
public let nutriscoreGrade: String?
|
public let nutriscoreGrade: String?
|
||||||
|
public let nutriscoreData: NutriscoreData?
|
||||||
public let novaGroup: Int?
|
public let novaGroup: Int?
|
||||||
|
public let ecoscoreGrade: String?
|
||||||
|
|
||||||
// Nutriments
|
public let nutritionDataPer: String?
|
||||||
public let nutriments: Nutriments?
|
public let nutriments: Nutriments?
|
||||||
public let ingredients: [Ingredient]?
|
public let nutrientLevels: NutrientLevels?
|
||||||
|
|
||||||
|
// MARK: - Ingredients & Allergens
|
||||||
|
public let ingredientsText: String?
|
||||||
|
public let ingredients: [Ingredient]?
|
||||||
|
public let ingredientsHierarchy: [String]?
|
||||||
|
public let allergens: String?
|
||||||
|
public let allergensHierarchy: [String]?
|
||||||
|
|
||||||
// MARK: - Product-Misc
|
|
||||||
public let additivesN: Int?
|
public let additivesN: Int?
|
||||||
|
|
||||||
|
// MARK: - Metadata
|
||||||
public let checked: String?
|
public let checked: String?
|
||||||
public let complete: Int?
|
public let complete: Int?
|
||||||
public let completeness: Float?
|
public let completeness: Float?
|
||||||
public let ecoscoreGrade: String?
|
public var categories: String?
|
||||||
// ...
|
public var categoriesHierarchy: [String]?
|
||||||
public let nutrientLevels: NutrientLevels?
|
|
||||||
|
|
||||||
public var id: String {
|
public var id: String {
|
||||||
guard let code = code else {
|
guard let code = code else {
|
||||||
return UUID().uuidString
|
return UUID().uuidString
|
||||||
}
|
}
|
||||||
|
|
||||||
return code
|
return code
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Computed Properties (Units)
|
||||||
|
public var waterQuantity: Units.UnitValue<Double>? {
|
||||||
|
guard let ingredient = ingredients?.first(where: { $0.isWater }) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let quantity = productQuantity,
|
||||||
|
let estimate = ingredient.percentEstimate,
|
||||||
|
let rawUnit = productQuantityUnit,
|
||||||
|
let unit = Units.Unit(rawValue: rawUnit)
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return Double(quantity * (estimate / 100))[unit]
|
||||||
|
}
|
||||||
|
|
||||||
|
public var isLiquid: Bool {
|
||||||
|
guard let rawUnit = productQuantityUnit,
|
||||||
|
let unit = Units.Unit(rawValue: rawUnit)
|
||||||
|
else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return unit.category == .volume
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - CodingKeys
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
case code
|
case code
|
||||||
case productName = "product_name"
|
case productName = "product_name"
|
||||||
|
case genericName = "generic_name"
|
||||||
case brands
|
case brands
|
||||||
|
case brandsTags = "brands_tags"
|
||||||
case quantity
|
case quantity
|
||||||
|
|
||||||
|
// Images
|
||||||
case imageFrontUrl = "image_front_url"
|
case imageFrontUrl = "image_front_url"
|
||||||
case imageSmallUrl = "image_small_url"
|
case imageSmallUrl = "image_small_url"
|
||||||
|
case imageUrl = "image_url"
|
||||||
|
case imageNutritionSmallUrl = "image_nutrition_small_url"
|
||||||
|
case imageNutritionThumbUrl = "image_nutrition_thumb_url"
|
||||||
|
case imageNutritionUrl = "image_nutrition_url"
|
||||||
|
case imageIngredientsSmallUrl = "image_ingredients_small_url"
|
||||||
|
case imageIngredientsThumbUrl = "image_ingredients_thumb_url"
|
||||||
|
case imageIngredientsUrl = "image_ingredients_url"
|
||||||
|
|
||||||
|
// Quantities
|
||||||
|
case productQuantity = "product_quantity"
|
||||||
|
case productQuantityUnit = "product_quantity_unit"
|
||||||
|
case servingQuantity = "serving_quantity"
|
||||||
|
case servingQuantityUnit = "serving_quantity_unit"
|
||||||
|
case servingSize = "serving_size"
|
||||||
|
|
||||||
case ingredientsText = "ingredients_text"
|
case ingredientsText = "ingredients_text"
|
||||||
|
case ingredients
|
||||||
|
case ingredientsHierarchy = "ingredients_hierarchy"
|
||||||
|
|
||||||
|
// Grades & Data
|
||||||
case nutriscoreGrade = "nutriscore_grade"
|
case nutriscoreGrade = "nutriscore_grade"
|
||||||
|
case nutriscoreData = "nutriscore_data"
|
||||||
case ecoscoreGrade = "ecoscore_grade"
|
case ecoscoreGrade = "ecoscore_grade"
|
||||||
case novaGroup = "nova_group"
|
case novaGroup = "nova_group"
|
||||||
case nutriments
|
case nutriments
|
||||||
case ingredients
|
case nutrientLevels = "nutrient_levels"
|
||||||
|
case nutritionDataPer = "nutrition_data_per"
|
||||||
|
|
||||||
case complete
|
case complete
|
||||||
case completeness
|
case completeness
|
||||||
case additivesN = "additives_n"
|
case additivesN = "additives_n"
|
||||||
case checked
|
case checked
|
||||||
case nutrientLevels = "nutrient_levels"
|
|
||||||
|
case allergens
|
||||||
|
case allergensHierarchy = "allergens_hierarchy"
|
||||||
|
case categories
|
||||||
|
case categoriesHierarchy = "categories_hierarchy"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Initializer
|
||||||
public init(from decoder: any Decoder) throws {
|
public init(from decoder: any Decoder) throws {
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
code = try container.decodeIfPresent(String.self, forKey: .code)
|
code = try container.decodeIfPresent(String.self, forKey: .code)
|
||||||
productName = try container.decodeIfPresent(
|
productName = try container.decodeIfPresent(
|
||||||
String.self, forKey: .productName)
|
String.self, forKey: .productName)
|
||||||
|
genericName = try container.decodeIfPresent(
|
||||||
|
String.self, forKey: .genericName)
|
||||||
brands = try container.decodeIfPresent(String.self, forKey: .brands)
|
brands = try container.decodeIfPresent(String.self, forKey: .brands)
|
||||||
|
brandsTags = try container.decodeIfPresent(
|
||||||
|
[String].self, forKey: .brandsTags)
|
||||||
quantity = try container.decodeIfPresent(String.self, forKey: .quantity)
|
quantity = try container.decodeIfPresent(String.self, forKey: .quantity)
|
||||||
|
|
||||||
|
// Images
|
||||||
imageFrontUrl = try container.decodeIfPresent(
|
imageFrontUrl = try container.decodeIfPresent(
|
||||||
String.self, forKey: .imageFrontUrl)
|
String.self, forKey: .imageFrontUrl)
|
||||||
imageSmallUrl = try container.decodeIfPresent(
|
imageSmallUrl = try container.decodeIfPresent(
|
||||||
String.self, forKey: .imageSmallUrl)
|
String.self, forKey: .imageSmallUrl)
|
||||||
|
imageUrl = try container.decodeIfPresent(String.self, forKey: .imageUrl)
|
||||||
|
imageNutritionSmallUrl = try container.decodeIfPresent(
|
||||||
|
String.self, forKey: .imageNutritionSmallUrl)
|
||||||
|
imageNutritionThumbUrl = try container.decodeIfPresent(
|
||||||
|
String.self, forKey: .imageNutritionThumbUrl)
|
||||||
|
imageNutritionUrl = try container.decodeIfPresent(
|
||||||
|
String.self, forKey: .imageNutritionUrl)
|
||||||
|
imageIngredientsSmallUrl = try container.decodeIfPresent(
|
||||||
|
String.self, forKey: .imageIngredientsSmallUrl)
|
||||||
|
imageIngredientsThumbUrl = try container.decodeIfPresent(
|
||||||
|
String.self, forKey: .imageIngredientsThumbUrl)
|
||||||
|
imageIngredientsUrl = try container.decodeIfPresent(
|
||||||
|
String.self, forKey: .imageIngredientsUrl)
|
||||||
|
|
||||||
|
// Quantities (Robust decoding)
|
||||||
|
productQuantity = try container.decodeFloatOrString(
|
||||||
|
forKey: .productQuantity)
|
||||||
|
productQuantityUnit = try container.decodeIfPresent(
|
||||||
|
String.self, forKey: .productQuantityUnit)
|
||||||
|
|
||||||
|
// servingQuantity is String?, but API might return a number
|
||||||
|
if let floatVal = try? container.decode(
|
||||||
|
Float.self, forKey: .servingQuantity)
|
||||||
|
{
|
||||||
|
servingQuantity = String(floatVal)
|
||||||
|
} else {
|
||||||
|
servingQuantity = try container.decodeIfPresent(
|
||||||
|
String.self, forKey: .servingQuantity)
|
||||||
|
}
|
||||||
|
|
||||||
|
servingQuantityUnit = try container.decodeIfPresent(
|
||||||
|
String.self, forKey: .servingQuantityUnit)
|
||||||
|
servingSize = try container.decodeIfPresent(
|
||||||
|
String.self, forKey: .servingSize)
|
||||||
|
|
||||||
|
// Ingredients & Categories
|
||||||
ingredientsText = try container.decodeIfPresent(
|
ingredientsText = try container.decodeIfPresent(
|
||||||
String.self, forKey: .ingredientsText)
|
String.self, forKey: .ingredientsText)
|
||||||
|
|
||||||
// Grades
|
|
||||||
nutriscoreGrade = try container.decodeIfPresent(
|
|
||||||
String.self, forKey: .nutriscoreGrade)
|
|
||||||
novaGroup = try container.decodeIfPresent(
|
|
||||||
Int.self, forKey: .novaGroup)
|
|
||||||
|
|
||||||
// Nutriments
|
|
||||||
nutriments = try container.decodeIfPresent(
|
|
||||||
Nutriments.self, forKey: .nutriments)
|
|
||||||
ingredients = try container.decodeIfPresent(
|
ingredients = try container.decodeIfPresent(
|
||||||
[Ingredient].self, forKey: .ingredients)
|
[Ingredient].self, forKey: .ingredients)
|
||||||
|
ingredientsHierarchy = try container.decodeIfPresent(
|
||||||
|
[String].self, forKey: .ingredientsHierarchy)
|
||||||
|
|
||||||
// MARK: - Product-Misc
|
categories = try container.decodeIfPresent(
|
||||||
|
String.self, forKey: .categories)
|
||||||
|
categoriesHierarchy = try container.decodeIfPresent(
|
||||||
|
[String].self, forKey: .categoriesHierarchy)
|
||||||
|
|
||||||
|
// Grades & Nutriments
|
||||||
|
nutriscoreGrade = try container.decodeIfPresent(
|
||||||
|
String.self, forKey: .nutriscoreGrade)
|
||||||
|
nutriscoreData = try container.decodeIfPresent(
|
||||||
|
NutriscoreData.self, forKey: .nutriscoreData)
|
||||||
|
novaGroup = try container.decodeIfPresent(Int.self, forKey: .novaGroup)
|
||||||
|
ecoscoreGrade = try container.decodeIfPresent(
|
||||||
|
String.self, forKey: .ecoscoreGrade)
|
||||||
|
|
||||||
|
nutriments = try container.decodeIfPresent(
|
||||||
|
Nutriments.self, forKey: .nutriments)
|
||||||
|
nutrientLevels = try container.decodeIfPresent(
|
||||||
|
NutrientLevels.self, forKey: .nutrientLevels)
|
||||||
|
nutritionDataPer = try container.decodeIfPresent(
|
||||||
|
String.self, forKey: .nutritionDataPer)
|
||||||
|
|
||||||
|
// Misc
|
||||||
additivesN = try container.decodeIfPresent(
|
additivesN = try container.decodeIfPresent(
|
||||||
Int.self, forKey: .additivesN)
|
Int.self, forKey: .additivesN)
|
||||||
checked = try container.decodeIfPresent(String.self, forKey: .checked)
|
checked = try container.decodeIfPresent(String.self, forKey: .checked)
|
||||||
complete = try container.decodeIfPresent(Int.self, forKey: .complete)
|
complete = try container.decodeIfPresent(Int.self, forKey: .complete)
|
||||||
completeness = try container.decodeFloatOrString(forKey: .completeness)
|
completeness = try container.decodeFloatOrString(forKey: .completeness)
|
||||||
ecoscoreGrade = try container.decodeIfPresent(
|
|
||||||
String.self, forKey: .ecoscoreGrade)
|
|
||||||
// ...
|
|
||||||
nutrientLevels = try container.decodeIfPresent(
|
|
||||||
NutrientLevels.self, forKey: .nutrientLevels)
|
|
||||||
|
|
||||||
|
allergens = try container.decodeIfPresent(
|
||||||
|
String.self, forKey: .allergens)
|
||||||
|
allergensHierarchy = try container.decodeIfPresent(
|
||||||
|
[String].self, forKey: .allergensHierarchy)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ final class OpenFoodFactsTests: XCTestCase {
|
|||||||
.tag(tag: .brands, value: "milka"),
|
.tag(tag: .brands, value: "milka"),
|
||||||
.pageSize(5),
|
.pageSize(5),
|
||||||
.sort(.popularity),
|
.sort(.popularity),
|
||||||
fields: [.nutrientLevels]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
let jsonResults = try JSONEncoder().encode(results)
|
let jsonResults = try JSONEncoder().encode(results)
|
||||||
|
|||||||
Reference in New Issue
Block a user