Probleme de struct decodable besoin d'expert

Bonjour,

j’ai besoin de votre aide je n’arrive pas à décoder un json avec Xcode Swift UI
je suis très nul dans cet exercice

import Foundation
struct musee:Decodable {
var region_administrative:String
var departement:String
var identifiant_museofile:String
var commune:String
var nom_officiel_du_musee:String
var adresse:String
var code_postal:String
var telephone:String
var url:String
var latitude:Double
var longitude:Double
var geolocalisation:geolocalisation
}
struct geolocalisation:Decodable {
var lon:Double
var lat:Double
}


import Foundation
import SwiftUI

@Observable
class AccesDonnees {

var listeMusee:[musee] = []
var chargementDonnees:Bool = false
var ChargementExplication = "Chargement en cours"

func connexionJson() async  {
    let statusTache = Task {() in
        // verification chaine de type url
        guard let urlString = URL(string: "https://data.culture.gouv.fr/api/explore/v2.1/catalog/datasets/liste-et-localisation-des-musees-de-france/records?limit=20") else {return}
        do {
            // connexion url session
            let (mesDonnees, _) = try await URLSession.shared.data(from: urlString)
            let musees = try JSONDecoder().decode(musee.self, from: mesDonnees)
            print("contenu des donnees :\(musees)")
        
        } catch {
            print(error)
            print(error.localizedDescription)
            
        }
    }
    let resultatTache = await statusTache.result
    switch resultatTache {
    case .success( _):
        await MainActor.run {
           // self.ChargementExplication = "Chargement terminé"

// DispatchQueue.main.asyncAfter(deadline: .now() + 1.7) {
// self.chargementDonnees = true
// }
}
//} // fin de la tâche

    case .failure(let error):
        await MainActor.run {

// self.ChargementExplication = « Error: (error.localizedDescription) »
// self.chargementDonnees = false
}
}

}

}

import SwiftUI
import MapKit

struct ContentView: View {
var accesDonnees:AccesDonnees = AccesDonnees()
var body: some View {
VStack {
Image(systemName: « globe »)
.imageScale(.large)
.foregroundStyle(.tint)
Text(« Hello, world! »)
}
.onAppear {
Task {
await accesDonnees.connexionJson()
}
}
.padding()
}
}

l'erreur que me retourne la console :
keyNotFound(CodingKeys(stringValue: "region_administrative", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"region_administrative\", intValue: nil) (\"region_administrative\").", underlyingError: nil))

The data couldn’t be read because it is missing.

un navigateur me retourne bien les données avec l'url https://data.culture.gouv.fr/api/explore/v2.1/catalog/datasets/liste-et-localisation-des-musees-de-france/records?limit=20
donc je me dit que l'erreur vient de mon fichier struct  ou de ma ligne pour décodé 
 let musees = try JSONDecoder().decode(musee.self, from: mesDonnees)

Merci de votre aide

Le JSON du site data.culture.gouv.fr est un nested JSON.

Screenshot 2024-03-23 at 09.16.41

Il te retourne un tableau « Results » avec les éléments que tu traites dedans.

Il faut que tu le traites en conséquence, en créant un tableau « Results » qui contiendra tes données.
Tu as un exemple ici :

Ce qui donnerait une struct en plus genre :

Struct Results: Codable {
var ListeMusee: [musee]
}

A ce moment la, il devrait pouvoir lire le contenu du JSON en traitant la struct Results. dans le ‹ JSONDecoder() ›
Petites remarques : attention au nommage de tes class/struct → avec un Majuscule au début.
Ton JSON est rempli, mais si un des éléments ne contient rien? il faudrait mieux passer par du conditionnel avec un retour erreur si jamais l’un des éléments est ‹ nil ›.

Merci @Mrt1, je regarde

Salut @titastus,

@Mrt1 a déjà trouvé d’où venait le problème mais, comme moi, tu es « nul » dans cet exercice de coder/decoder un json, je te conseille ce site internet qui fait (presque) tout pour toi !

Tu copies le json dans la fenêtre de gauche et paramètre quelques options dans le volet de droite pour obtenir le/les classes/structs a créer.

Par example, les fichiers proposés pour ton API sont :

Musees.swift

import Foundation

// MARK: - Musees
struct Musees: Codable, Hashable {
    let totalCount: Int
    let results: [Result]

    enum CodingKeys: String, CodingKey {
        case totalCount = "total_count"
        case results = "results"
    }
}

// MARK: Musees convenience initializers and mutators

extension Musees {
    init(data: Data) throws {
        self = try newJSONDecoder().decode(Musees.self, from: data)
    }

    init(_ json: String, using encoding: String.Encoding = .utf8) throws {
        guard let data = json.data(using: encoding) else {
            throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
        }
        try self.init(data: data)
    }

    init(fromURL url: URL) throws {
        try self.init(data: try Data(contentsOf: url))
    }

    func with(
        totalCount: Int? = nil,
        results: [Result]? = nil
    ) -> Musees {
        return Musees(
            totalCount: totalCount ?? self.totalCount,
            results: results ?? self.results
        )
    }

    func jsonData() throws -> Data {
        return try newJSONEncoder().encode(self)
    }

    func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
        return String(data: try self.jsonData(), encoding: encoding)
    }
}

Result.swift

import Foundation

// MARK: - Result
struct Result: Codable, Hashable {
    let regionAdministrative: RegionAdministrative
    let departement: String
    let identifiantMuseofile: String
    let commune: String
    let nomOfficielDuMusee: String
    let adresse: String?
    let lieu: String?
    let codePostal: String
    let telephone: String
    let url: String?
    let latitude: Double
    let longitude: Double
    let refDeps: String
    let geolocalisation: Geolocalisation
    let dateArreteAttributionAppellation: String

    enum CodingKeys: String, CodingKey {
        case regionAdministrative = "region_administrative"
        case departement = "departement"
        case identifiantMuseofile = "identifiant_museofile"
        case commune = "commune"
        case nomOfficielDuMusee = "nom_officiel_du_musee"
        case adresse = "adresse"
        case lieu = "lieu"
        case codePostal = "code_postal"
        case telephone = "telephone"
        case url = "url"
        case latitude = "latitude"
        case longitude = "longitude"
        case refDeps = "ref_deps"
        case geolocalisation = "geolocalisation"
        case dateArreteAttributionAppellation = "date_arrete_attribution_appellation"
    }
}

// MARK: Result convenience initializers and mutators

extension Result {
    init(data: Data) throws {
        self = try newJSONDecoder().decode(Result.self, from: data)
    }

    init(_ json: String, using encoding: String.Encoding = .utf8) throws {
        guard let data = json.data(using: encoding) else {
            throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
        }
        try self.init(data: data)
    }

    init(fromURL url: URL) throws {
        try self.init(data: try Data(contentsOf: url))
    }

    func with(
        regionAdministrative: RegionAdministrative? = nil,
        departement: String? = nil,
        identifiantMuseofile: String? = nil,
        commune: String? = nil,
        nomOfficielDuMusee: String? = nil,
        adresse: String?? = nil,
        lieu: String?? = nil,
        codePostal: String? = nil,
        telephone: String? = nil,
        url: String?? = nil,
        latitude: Double? = nil,
        longitude: Double? = nil,
        refDeps: String? = nil,
        geolocalisation: Geolocalisation? = nil,
        dateArreteAttributionAppellation: String? = nil
    ) -> Result {
        return Result(
            regionAdministrative: regionAdministrative ?? self.regionAdministrative,
            departement: departement ?? self.departement,
            identifiantMuseofile: identifiantMuseofile ?? self.identifiantMuseofile,
            commune: commune ?? self.commune,
            nomOfficielDuMusee: nomOfficielDuMusee ?? self.nomOfficielDuMusee,
            adresse: adresse ?? self.adresse,
            lieu: lieu ?? self.lieu,
            codePostal: codePostal ?? self.codePostal,
            telephone: telephone ?? self.telephone,
            url: url ?? self.url,
            latitude: latitude ?? self.latitude,
            longitude: longitude ?? self.longitude,
            refDeps: refDeps ?? self.refDeps,
            geolocalisation: geolocalisation ?? self.geolocalisation,
            dateArreteAttributionAppellation: dateArreteAttributionAppellation ?? self.dateArreteAttributionAppellation
        )
    }

    func jsonData() throws -> Data {
        return try newJSONEncoder().encode(self)
    }

    func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
        return String(data: try self.jsonData(), encoding: encoding)
    }
}

Geolocalisation.swift

import Foundation

// MARK: - Geolocalisation
struct Geolocalisation: Codable, Hashable {
    let lon: Double
    let lat: Double

    enum CodingKeys: String, CodingKey {
        case lon = "lon"
        case lat = "lat"
    }
}

// MARK: Geolocalisation convenience initializers and mutators

extension Geolocalisation {
    init(data: Data) throws {
        self = try newJSONDecoder().decode(Geolocalisation.self, from: data)
    }

    init(_ json: String, using encoding: String.Encoding = .utf8) throws {
        guard let data = json.data(using: encoding) else {
            throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
        }
        try self.init(data: data)
    }

    init(fromURL url: URL) throws {
        try self.init(data: try Data(contentsOf: url))
    }

    func with(
        lon: Double? = nil,
        lat: Double? = nil
    ) -> Geolocalisation {
        return Geolocalisation(
            lon: lon ?? self.lon,
            lat: lat ?? self.lat
        )
    }

    func jsonData() throws -> Data {
        return try newJSONEncoder().encode(self)
    }

    func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
        return String(data: try self.jsonData(), encoding: encoding)
    }
}

RegionAdministrative.swift

import Foundation

enum RegionAdministrative: String, Codable, Hashable {
    case auvergneRhôneAlpes = "Auvergne-Rhône-Alpes"
    case bourgogneFrancheComté = "Bourgogne-Franche-Comté"
    case bretagne = "Bretagne"
    case centreValDeLoire = "Centre-Val de Loire"
    case corse = "Corse"
    case grandEst = "Grand Est"
    case hautsDeFrance = "Hauts-de-France"
}

JSONSchemaSupport.swift

import Foundation

// MARK: - Helper functions for creating encoders and decoders

func newJSONDecoder() -> JSONDecoder {
    let decoder = JSONDecoder()
    if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) {
        decoder.dateDecodingStrategy = .iso8601
    }
    return decoder
}

func newJSONEncoder() -> JSONEncoder {
    let encoder = JSONEncoder()
    if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) {
        encoder.dateEncodingStrategy = .iso8601
    }
    return encoder
}

Quand je dis qu’il y a quand même un peu de travail manuel à faire, je fais référence par example au fichier RegionAdministrative.swift qui n’est pas exhaustif (requête sur API est limitée a 100 enregistrements) ou encore aux caractères spéciaux des variables auvergneRhôneAlpes ou bourgogneFrancheComté que je supprimerais…

Cedric

2 « J'aime »