// This file was generated from JSON Schema using quicktype, do not modify it directly. // To parse the JSON, add this file to your project and do: // // let federalTournament = try? JSONDecoder().decode(FederalTournament.self, from: jsonData) import Foundation import CoreLocation import LeStorage import PadelClubData // MARK: - FederalTournament struct FederalTournament: Identifiable, Codable, Hashable { func getEvent() -> Event { let club = DataStore.shared.user.clubsObjects().first(where: { $0.code == codeClub }) var event = DataStore.shared.events.first(where: { $0.tenupId == id.string }) if event == nil { event = Event(creator: StoreCenter.main.userId, club: club?.id, name: libelle, tenupId: id.string) do { try DataStore.shared.events.addOrUpdate(instance: event!) } catch { Logger.error(error) } } if let club, club.creator == nil { club.creator = StoreCenter.main.userId do { try DataStore.shared.clubs.addOrUpdate(instance: club) } catch { Logger.error(error) } } return event! } static func sectionedData(from federalTournaments: [FederalTournament]) -> [String: [FederalTournament]] { Dictionary(grouping: federalTournaments) { federalTournament in URL.importDateFormatter.string(from: (federalTournament.dateDebut ?? .distantFuture)) } } let id: String var millesime: Int? var libelle: String? var tmc: Bool? var tarifAdulteChampionnat: Double? var type: String? var ageReel: Bool? var naturesTerrains: [JSONAny]? var idsArbitres: [JSONAny]? var tarifJeuneChampionnat: Double? var international, inscriptionEnLigne: Bool? var categorieTournoi: CategorieTournoi? var prixLot: Int? var paiementEnLigne: Bool? var reductionAdherentJeune, reductionAdherentAdulte: Double? var paiementEnLigneObligatoire: Bool? var villeEngagement: String? var senior, veteran, inscriptionEnLigneEnCours, avecResultatPublie: Bool? var code: String? var categorieAge: CategorieAge? var codeComite: String? var installations: [JSONAny]? var reductionEpreuveSupplementaireJeune, reductionEpreuveSupplementaireAdulte: Double? var nomComite: String? var naturesEpreuves: [Serie]? var jeune: Bool? var courrielEngagement, nomClub: String? var installation: Installation? var categorieAgeMax: CategorieAge? var tournoiInterne: Bool? var nomLigue, nomEngagement, codeLigue: String? var modeleDeBalle: ModeleDeBalle? var jugeArbitre: JugeArbitre? var adresse2Engagement: String? var epreuves: [Epreuve]? var dateDebut: Date? var serie: Serie? var dateFin, dateValidation: Date? var codePostalEngagement, codeClub: String? var prixEspece: Int? var japPhoneNumber: String? mutating func updateJapPhoneNumber(phone: String?) { self.japPhoneNumber = phone } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) // Handle id that could be string or int if let idString = try? container.decode(String.self, forKey: .id) { id = idString } else if let idInt = try? container.decode(Int.self, forKey: .id) { id = String(idInt) } else { throw DecodingError.dataCorruptedError(forKey: .id, in: container, debugDescription: "Expected String or Int for id") } // Regular decoding for simple properties millesime = try container.decodeIfPresent(Int.self, forKey: .millesime) libelle = try container.decodeIfPresent(String.self, forKey: .libelle) tmc = try container.decodeIfPresent(Bool.self, forKey: .tmc) tarifAdulteChampionnat = try container.decodeIfPresent(Double.self, forKey: .tarifAdulteChampionnat) type = try container.decodeIfPresent(String.self, forKey: .type) ageReel = try container.decodeIfPresent(Bool.self, forKey: .ageReel) naturesTerrains = try container.decodeIfPresent([JSONAny].self, forKey: .naturesTerrains) idsArbitres = try container.decodeIfPresent([JSONAny].self, forKey: .idsArbitres) tarifJeuneChampionnat = try container.decodeIfPresent(Double.self, forKey: .tarifJeuneChampionnat) international = try container.decodeIfPresent(Bool.self, forKey: .international) inscriptionEnLigne = try container.decodeIfPresent(Bool.self, forKey: .inscriptionEnLigne) categorieTournoi = try container.decodeIfPresent(CategorieTournoi.self, forKey: .categorieTournoi) prixLot = try container.decodeIfPresent(Int.self, forKey: .prixLot) paiementEnLigne = try container.decodeIfPresent(Bool.self, forKey: .paiementEnLigne) reductionAdherentJeune = try container.decodeIfPresent(Double.self, forKey: .reductionAdherentJeune) reductionAdherentAdulte = try container.decodeIfPresent(Double.self, forKey: .reductionAdherentAdulte) paiementEnLigneObligatoire = try container.decodeIfPresent(Bool.self, forKey: .paiementEnLigneObligatoire) villeEngagement = try container.decodeIfPresent(String.self, forKey: .villeEngagement) senior = try container.decodeIfPresent(Bool.self, forKey: .senior) veteran = try container.decodeIfPresent(Bool.self, forKey: .veteran) inscriptionEnLigneEnCours = try container.decodeIfPresent(Bool.self, forKey: .inscriptionEnLigneEnCours) avecResultatPublie = try container.decodeIfPresent(Bool.self, forKey: .avecResultatPublie) code = try container.decodeIfPresent(String.self, forKey: .code) categorieAge = try container.decodeIfPresent(CategorieAge.self, forKey: .categorieAge) codeComite = try container.decodeIfPresent(String.self, forKey: .codeComite) installations = try container.decodeIfPresent([JSONAny].self, forKey: .installations) reductionEpreuveSupplementaireJeune = try container.decodeIfPresent(Double.self, forKey: .reductionEpreuveSupplementaireJeune) reductionEpreuveSupplementaireAdulte = try container.decodeIfPresent(Double.self, forKey: .reductionEpreuveSupplementaireAdulte) nomComite = try container.decodeIfPresent(String.self, forKey: .nomComite) naturesEpreuves = try container.decodeIfPresent([Serie].self, forKey: .naturesEpreuves) jeune = try container.decodeIfPresent(Bool.self, forKey: .jeune) courrielEngagement = try container.decodeIfPresent(String.self, forKey: .courrielEngagement) nomClub = try container.decodeIfPresent(String.self, forKey: .nomClub) installation = try container.decodeIfPresent(Installation.self, forKey: .installation) categorieAgeMax = try container.decodeIfPresent(CategorieAge.self, forKey: .categorieAgeMax) tournoiInterne = try container.decodeIfPresent(Bool.self, forKey: .tournoiInterne) nomLigue = try container.decodeIfPresent(String.self, forKey: .nomLigue) nomEngagement = try container.decodeIfPresent(String.self, forKey: .nomEngagement) codeLigue = try container.decodeIfPresent(String.self, forKey: .codeLigue) modeleDeBalle = try container.decodeIfPresent(ModeleDeBalle.self, forKey: .modeleDeBalle) jugeArbitre = try container.decodeIfPresent(JugeArbitre.self, forKey: .jugeArbitre) adresse2Engagement = try container.decodeIfPresent(String.self, forKey: .adresse2Engagement) epreuves = try container.decodeIfPresent([Epreuve].self, forKey: .epreuves) serie = try container.decodeIfPresent(Serie.self, forKey: .serie) codePostalEngagement = try container.decodeIfPresent(String.self, forKey: .codePostalEngagement) codeClub = try container.decodeIfPresent(String.self, forKey: .codeClub) prixEspece = try container.decodeIfPresent(Int.self, forKey: .prixEspece) // Custom decoding for dateDebut if let dateContainer = try? container.nestedContainer(keyedBy: DateKeys.self, forKey: .dateDebut) { if let dateString = try? dateContainer.decode(String.self, forKey: .date) { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSSSSS" dateDebut = dateFormatter.date(from: dateString) } } // Custom decoding for dateFin if let dateContainer = try? container.nestedContainer(keyedBy: DateKeys.self, forKey: .dateFin) { if let dateString = try? dateContainer.decode(String.self, forKey: .date) { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSSSSS" dateFin = dateFormatter.date(from: dateString) } } // Custom decoding for dateValidation if let dateContainer = try? container.nestedContainer(keyedBy: DateKeys.self, forKey: .dateValidation) { if let dateString = try? dateContainer.decode(String.self, forKey: .date) { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSSSSS" dateValidation = dateFormatter.date(from: dateString) } } } private enum DateKeys: String, CodingKey { case date } var dayPeriod: DayPeriod { if let dateDebut { let day = dateDebut.get(.weekday) switch day { case 2...6: return .week default: return .weekend } } return .all } var dayDuration: Int { if let dateDebut, let dateFin { return Calendar.current.numberOfDaysBetween(dateDebut, and: dateFin) + 1 } else { return 1 } } var city: String? { if let installation { return installation.ville } return nil } var location: CLLocation? { if let lat = installation?.lat, let lng = installation?.lng { return CLLocation(latitude: lat, longitude: lng) } return nil } var computedStartDate: String? { dateDebut?.formatted(.dateTime.day(.defaultDigits).month(.abbreviated).year()) } var tournaments: [any TournamentBuildHolder] { epreuves? .compactMap({ $0.tournamentBuild }) .sorted(using: .keyPath(\TournamentBuild.level.order), .keyPath(\TournamentBuild.category.order), .keyPath(\TournamentBuild.age.order)) ?? [] } var federalClub: FederalClub? { if let codeClub { return FederalClub(federalClubCode: codeClub, federalClubName: clubLabel()) } else { return nil } } var shareMessage: String { [libelle, dateDebut?.formatted(date: .complete, time: .omitted)].compactMap({$0}).joined(separator: "\n") + "\n" } var sharePartnerMessage: String { ["Je nous ai inscris au tournoi suivant : ", libelle, dateDebut?.formatted(date: .complete, time: .omitted), "message preparé par Padel Club", URLs.appStore.rawValue ].compactMap({$0}).joined(separator: "\n") + "\n" } func calendarNoteMessage() -> String { [jugeArbitre?.nom, jugeArbitre?.prenom, courrielEngagement, installation?.telephone].compactMap({$0}).joined(separator: "\n") } var japMessage: String { [nomClub, jugeArbitre?.nom, jugeArbitre?.prenom, courrielEngagement, japPhoneNumber].compactMap({$0}).joined(separator: ";") } func umpireLabel() -> String { [jugeArbitre?.nom, jugeArbitre?.prenom].compactMap({$0}).map({ $0.lowercased().capitalized }).joined(separator: " ") } func phoneLabel() -> String { [installation?.telephone].compactMap({$0}).joined(separator: " ") } func mailLabel() -> String { [courrielEngagement].compactMap({$0}).joined(separator: " ") } func validForSearch(_ searchText: String, scope: FederalTournamentSearchScope) -> Bool { var trimmedSearchText = searchText.lowercased().trimmingCharacters(in: .whitespaces).folding(options: .diacriticInsensitive, locale: .current) trimmedSearchText = trimmedSearchText.replaceCharactersFromSet(characterSet: .punctuationCharacters, replacementString: " ") trimmedSearchText = trimmedSearchText.replaceCharactersFromSet(characterSet: .symbols, replacementString: " ") switch scope { case .club: return nomClub?.canonicalVersion.localizedCaseInsensitiveContains(trimmedSearchText) == true case .all: return libelle?.canonicalVersion.localizedCaseInsensitiveContains(trimmedSearchText) == true || nomClub?.canonicalVersion.localizedCaseInsensitiveContains(trimmedSearchText) == true || nomLigue?.canonicalVersion.localizedCaseInsensitiveContains(trimmedSearchText) == true || jugeArbitre?.nom?.canonicalVersion.localizedCaseInsensitiveContains(trimmedSearchText) == true || jugeArbitre?.prenom?.canonicalVersion.localizedCaseInsensitiveContains(trimmedSearchText) == true case .ligue: return nomLigue?.canonicalVersion.localizedCaseInsensitiveContains(trimmedSearchText) == true } } } extension FederalTournament: FederalTournamentHolder { var startDate: Date { dateDebut ?? .distantFuture } var endDate: Date? { dateFin } // var distance: Double? { distanceEnMetres } // var localizedLabel: String? { libelle } // var clubName: String? { nomClub } // var importedId: Int { id } var holderId: String { id.string } func clubLabel() -> String { nomClub ?? villeEngagement ?? installation?.nom ?? "" } func subtitleLabel(forBuild build: any TournamentBuildHolder) -> String { "" } func tournamentTitle(_ displayStyle: DisplayStyle, forBuild build: any TournamentBuildHolder) -> String { build.level.localizedLevelLabel(displayStyle) } func displayAgeAndCategory(forBuild build: any TournamentBuildHolder) -> Bool { true } } // MARK: - CategorieAge struct CategorieAge: Codable, Hashable { var ageJoueurMin, ageMin, ageJoueurMax, ageRechercheMax: Int? var categoriesAgeTypePratique: [CategoriesAgeTypePratique]? var ageMax: Int? var libelle: String? var id: Int? var valide, ageReel: Bool? var ageRechercheMin: Int? var homologable: Bool? var tournamentAge: FederalTournamentAge? { if let id { return FederalTournamentAge(rawValue: id) ?? .senior } if let libelle { return FederalTournamentAge.allCases.first(where: { $0.localizedFederalAgeLabel().localizedCaseInsensitiveContains(libelle) }) ?? .senior } return .senior } } // MARK: - CategoriesAgeTypePratique struct CategoriesAgeTypePratique: Codable, Hashable { var id: ID? } // MARK: - ID struct ID: Codable, Hashable { var typePratique: String? var idCategorieAge: Int? } // MARK: - CategorieTournoi struct CategorieTournoi: Codable, Hashable { var code, codeTaxe: String? var compteurGda: CompteurGda? var libelle, niveauHierarchique: String? var valide: Bool? } // MARK: - CompteurGda struct CompteurGda: Codable, Hashable { var classementMax: Classement? var libelle: String? var classementMin: Classement? } // MARK: - Classement struct Classement: Codable, Hashable { var nature, libelle: String? var serie: Serie? var sexe: String? var id: Int? var valide: Bool? var poidsDouble, echelon: Int? } // MARK: - Serie struct Serie: Codable, Hashable { var code, libelle: String? var valide: Bool? var sexe: String? var tournamentCategory: TournamentCategory? { TournamentCategory.allCases.first(where: { $0.requestLabel == code }) ?? .men } } // MARK: - Epreuve struct Epreuve: Codable, Hashable { var inscriptionEnLigneEnCours: Bool? var categorieAge: CategorieAge? var typeEpreuve: TypeEpreuve? var tarifAdulte: Double? var classementHaut: Classement? var nombreDecoupagesPublies: Int? var libelle: String? var participationAgeReel, clotureInscriptionSansPreavis: Bool? var dateCloture: Date? var publicationResultat: Bool? var classementBas: Classement? var tarifsSpecifiques: [JSONAny]? var homologuee: Bool? var dateDebut: Date? var tarifJeune: Double? var serie: Serie? var categorieAgeMax: CategorieAge? var dateFin: Date? var borneAnneesNaissance: BorneAnneesNaissance? var natureEpreuve: Serie? var epreuveOpen: Bool? enum CodingKeys: String, CodingKey { case inscriptionEnLigneEnCours, categorieAge, tarifAdulte, classementHaut, nombreDecoupagesPublies, libelle, participationAgeReel, clotureInscriptionSansPreavis, dateCloture, publicationResultat, classementBas, tarifsSpecifiques, homologuee, dateDebut, tarifJeune, serie, categorieAgeMax, dateFin, borneAnneesNaissance, natureEpreuve, typeEpreuve case epreuveOpen = "open" } var tournamentBuild: TournamentBuild? { if let age = categorieAge?.tournamentAge, let category = natureEpreuve?.tournamentCategory, let level = typeEpreuve?.tournamentLevel { return TournamentBuild(category: category, level: level, age: age) } return nil } } // MARK: - TypeEpreuve struct TypeEpreuve: Codable, Hashable { let code: String? let delai: Int? let libelle: String? let coefficient, montantMax, montantMin: Int? let valide: Bool? let echelon: Int? let typeHomologation: String? var tournamentLevel: TournamentLevel? { if let code, let value = Int(code.removingFirstCharacter) { return TournamentLevel(rawValue: value) ?? .p100 } return .p100 } } // MARK: - BorneAnneesNaissance struct BorneAnneesNaissance: Codable, Hashable { var min, max: Int? } // MARK: - Installation struct Installation: Codable, Hashable { var ville: String? var lng: Double? var surfaces: [JSONAny]? var vestiaires: Bool? var telephone, codePostal, nom, adresse1, adresse2: String? var lat: Double? var clubHouse: Bool? var libelle: String { [adresse1, adresse2, ville, codePostal].compactMap({ $0 }).joined(separator: " ") } } // MARK: - JugeArbitre struct JugeArbitre: Codable, Hashable { var idCRM, id: Int? var nom, prenom: String? enum CodingKeys: String, CodingKey { case idCRM = "idCrm" case id, nom, prenom } } // MARK: - ModeleDeBalle struct ModeleDeBalle: Codable, Hashable { var libelle: String? var marqueDeBalle: MarqueDeBalle? var id: Int? var valide, homologue: Bool? } // MARK: - MarqueDeBalle struct MarqueDeBalle: Codable, Hashable { var id: Int? var valide: Bool? var marque: String? } // MARK: - Encode/decode helpers class JSONNull: Codable, Hashable { public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool { return true } public var hashValue: Int { return 0 } public init() {} public required init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() if !container.decodeNil() { throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull")) } } public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encodeNil() } } class JSONCodingKey: CodingKey { let key: String required init?(intValue: Int) { return nil } required init?(stringValue: String) { key = stringValue } var intValue: Int? { return nil } var stringValue: String { return key } } class JSONAny: Codable, Hashable, Equatable { var value: Any init() { self.value = () } static func decodingError(forCodingPath codingPath: [CodingKey]) -> DecodingError { let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Cannot decode JSONAny") return DecodingError.typeMismatch(JSONAny.self, context) } static func encodingError(forValue value: Any, codingPath: [CodingKey]) -> EncodingError { let context = EncodingError.Context(codingPath: codingPath, debugDescription: "Cannot encode JSONAny") return EncodingError.invalidValue(value, context) } static func decode(from container: SingleValueDecodingContainer) throws -> Any { if let value = try? container.decode(Bool.self) { return value } if let value = try? container.decode(Int64.self) { return value } if let value = try? container.decode(Double.self) { return value } if let value = try? container.decode(String.self) { return value } if container.decodeNil() { return JSONNull() } throw decodingError(forCodingPath: container.codingPath) } static func decode(from container: inout UnkeyedDecodingContainer) throws -> Any { if let value = try? container.decode(Bool.self) { return value } if let value = try? container.decode(Int64.self) { return value } if let value = try? container.decode(Double.self) { return value } if let value = try? container.decode(String.self) { return value } if let value = try? container.decodeNil() { if value { return JSONNull() } } if var container = try? container.nestedUnkeyedContainer() { return try decodeArray(from: &container) } if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self) { return try decodeDictionary(from: &container) } throw decodingError(forCodingPath: container.codingPath) } static func decode(from container: inout KeyedDecodingContainer, forKey key: JSONCodingKey) throws -> Any { if let value = try? container.decode(Bool.self, forKey: key) { return value } if let value = try? container.decode(Int64.self, forKey: key) { return value } if let value = try? container.decode(Double.self, forKey: key) { return value } if let value = try? container.decode(String.self, forKey: key) { return value } if let value = try? container.decodeNil(forKey: key) { if value { return JSONNull() } } if var container = try? container.nestedUnkeyedContainer(forKey: key) { return try decodeArray(from: &container) } if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key) { return try decodeDictionary(from: &container) } throw decodingError(forCodingPath: container.codingPath) } static func decodeArray(from container: inout UnkeyedDecodingContainer) throws -> [Any] { var arr: [Any] = [] while !container.isAtEnd { let value = try decode(from: &container) arr.append(value) } return arr } static func decodeDictionary(from container: inout KeyedDecodingContainer) throws -> [String: Any] { var dict = [String: Any]() for key in container.allKeys { let value = try decode(from: &container, forKey: key) dict[key.stringValue] = value } return dict } static func encode(to container: inout UnkeyedEncodingContainer, array: [Any]) throws { for value in array { if let value = value as? Bool { try container.encode(value) } else if let value = value as? Int64 { try container.encode(value) } else if let value = value as? Double { try container.encode(value) } else if let value = value as? String { try container.encode(value) } else if value is JSONNull { try container.encodeNil() } else if let value = value as? [Any] { var container = container.nestedUnkeyedContainer() try encode(to: &container, array: value) } else if let value = value as? [String: Any] { var container = container.nestedContainer(keyedBy: JSONCodingKey.self) try encode(to: &container, dictionary: value) } else { throw encodingError(forValue: value, codingPath: container.codingPath) } } } static func encode(to container: inout KeyedEncodingContainer, dictionary: [String: Any]) throws { for (key, value) in dictionary { let key = JSONCodingKey(stringValue: key)! if let value = value as? Bool { try container.encode(value, forKey: key) } else if let value = value as? Int64 { try container.encode(value, forKey: key) } else if let value = value as? Double { try container.encode(value, forKey: key) } else if let value = value as? String { try container.encode(value, forKey: key) } else if value is JSONNull { try container.encodeNil(forKey: key) } else if let value = value as? [Any] { var container = container.nestedUnkeyedContainer(forKey: key) try encode(to: &container, array: value) } else if let value = value as? [String: Any] { var container = container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key) try encode(to: &container, dictionary: value) } else { throw encodingError(forValue: value, codingPath: container.codingPath) } } } static func encode(to container: inout SingleValueEncodingContainer, value: Any) throws { if let value = value as? Bool { try container.encode(value) } else if let value = value as? Int64 { try container.encode(value) } else if let value = value as? Double { try container.encode(value) } else if let value = value as? String { try container.encode(value) } else if value is JSONNull { try container.encodeNil() } else { throw encodingError(forValue: value, codingPath: container.codingPath) } } public required init(from decoder: Decoder) throws { if var arrayContainer = try? decoder.unkeyedContainer() { self.value = try JSONAny.decodeArray(from: &arrayContainer) } else if var container = try? decoder.container(keyedBy: JSONCodingKey.self) { self.value = try JSONAny.decodeDictionary(from: &container) } else { let container = try decoder.singleValueContainer() self.value = try JSONAny.decode(from: container) } } public func encode(to encoder: Encoder) throws { if let arr = self.value as? [Any] { var container = encoder.unkeyedContainer() try JSONAny.encode(to: &container, array: arr) } else if let dict = self.value as? [String: Any] { var container = encoder.container(keyedBy: JSONCodingKey.self) try JSONAny.encode(to: &container, dictionary: dict) } else { var container = encoder.singleValueContainer() try JSONAny.encode(to: &container, value: self.value) } } public static func == (lhs: JSONAny, rhs: JSONAny) -> Bool { switch (lhs.value, rhs.value) { case (let l as Bool, let r as Bool): return l == r case (let l as Int64, let r as Int64): return l == r case (let l as Double, let r as Double): return l == r case (let l as String, let r as String): return l == r case (let l as JSONNull, let r as JSONNull): return true case (let l as [Any], let r as [Any]): guard l.count == r.count else { return false } return zip(l, r).allSatisfy { (a, b) in // Recursively wrap in JSONAny for comparison JSONAny(value: a) == JSONAny(value: b) } case (let l as [String: Any], let r as [String: Any]): guard l.count == r.count else { return false } for (key, lVal) in l { guard let rVal = r[key], JSONAny(value: lVal) == JSONAny(value: rVal) else { return false } } return true default: return false } } public func hash(into hasher: inout Hasher) { switch value { case let v as Bool: hasher.combine(0) hasher.combine(v) case let v as Int64: hasher.combine(1) hasher.combine(v) case let v as Double: hasher.combine(2) hasher.combine(v) case let v as String: hasher.combine(3) hasher.combine(v) case is JSONNull: hasher.combine(4) case let v as [Any]: hasher.combine(5) for elem in v { JSONAny(value: elem).hash(into: &hasher) } case let v as [String: Any]: hasher.combine(6) // Order of hashing dictionary keys shouldn't matter for key in v.keys.sorted() { hasher.combine(key) if let val = v[key] { JSONAny(value: val).hash(into: &hasher) } } default: hasher.combine(-1) } } // Helper init for internal use convenience init(value: Any) { self.init() self.value = value } }