Lire un fichier json

Bonjour,
Je dois lire un fichier json qui a la forme suivante :

{"champ 1":"test 1", "champ 2":"test 2", "entete":["valeur 1", "valeur 2"], "donnees":[[0, True], [1, True], [0, False]], "champ 3":"test 3"}

J'arrive bien à récupérer test 1, test 2, test 3

Approximativement voici mon code :

struct ResponseData: Decodable {

let champ1: String
let champ2: String
let champ3: String

}

func loadJson(fileName: String) -> ResponseData? {
    if let url = Bundle.main.url(forResource: fileName, withExtension: "json") {
        do {
            let data = try Data(contentsOf: url)
            let decoder = JSONDecoder()
            let jsonData = try decoder.decode(ResponseData.self, from: data)
            return jsonData
        } catch {
            print("error:\(error)")
        }
    }
    return nil
}


guard let result = loadJson(fileName: "File")
        else { return }

print(result.champ1)
print(result.champ2)
print(result.champ3)

Cependant comment récupérer les valeurs entre crochets [0, True] etc...

Merci de votre aide.

Mots clés:

Réponses

  • Alors plusieurs choses m'embêtent.
    Tu devrais avoir des erreurs de thrown, non ?
    Les noms des variables champ1, champ2 et champ3 ne correspondent exactement au noms des clés champ 1 (y'a un espace), donc il faut rajouter une ligne pour dire à quelle clés en String cela correspond réellement.

    Ensuite, ["valeur 1", "valeur 2"], clairement, ça c'est un Array. Donc:
    let entente: [String] devrait faire l'affaire.
    Pour données, c'est du [Any] ou du [Bool], ou du [Int, puis Bool], donc là, y'aura un peu plus de travail.
    Ça dépend de ce que tu veux et de ce que cela représente réellement.

  • PyrohPyroh Membre
    juillet 2018 modifié #3

    Franchement ne t'embête pas à générer ça toi même.
    Utilise cet outil, il y a aussi une extension pour Xcode si besoin.

    Edit : par contre fais attention qu'en JSON true et false ne prennent pas de majuscule.

  • heliohelio Membre
    juillet 2018 modifié #4

    Merci !
    ah oui génial ton outil ! impressionnant !

    ça fonctionne, mais en revanche comment ça récupère toutes les infos "donnees":[[0, True], [1, True], [0, False]]
    je ne vois pas de boucle ! :D

  • CéroceCéroce Membre, Modérateur
    juillet 2018 modifié #5

    @helio a dit :
    ça fonctionne, mais en revanche comment ça récupère toutes les infos "donnees":[[0, True], [1, True], [0, False]]
    je ne vois pas de boucle ! :D

    C'est parce que la struct se conforme au protocole Codable. C'est le compilateur qui génère automatiquement le code. Cherche sur le web, il y a quelques articles qui expliquent cela fort bien, ainsi que les limites de ce mécanisme, notamment quand les noms des champs de la struct ne correspondent pas aux clés du JSON.

  • CéroceCéroce Membre, Modérateur

    Et voir aussi cet article même s'il ne parle pas de Codable, qui n'existait pas encore:
    https://developer.apple.com/swift/blog/?id=37

    Ça explique au moins clairement comment utiliser les optionals.

  • Merci Ceroce,
    en revanche j'ai un autre problème : les résultats de la partie "donnees" [[0, True], [1, True], [0, False]] que je souhaite afficher dans une NSTableView s'affichent de cette façon :

    double(0) 
    bool(true)
    double(1) 
    bool(true)
    double(0) 
    bool(false)
    
  • CéroceCéroce Membre, Modérateur

    Pourrions-nous avoir du code ?

  • voila le code :

     import Cocoa
     import CoreLocation
    
    class ViewController: NSViewController {
    
    @IBOutlet weak var tableView: NSTableView!
    @IBOutlet weak var labelProductName: NSTextField!
    @IBOutlet weak var labelPlace: NSTextField!
    
    var resultData : ResponseData? = nil
    var dataArray : [Int : String] = [ : ]
    
     override func viewDidLoad() {
        super.viewDidLoad()
    
        tableView.delegate = self
        tableView.dataSource = self
        tableView.target = self
    
    }
    
    override var representedObject: Any? {
        didSet {
        // Update the view, if already loaded.
    
    
            guard let result = loadJson(fileName: "File")
                    else { return }
                resultData = result
    
    
        guard let array = resultData?.detailsData[1] else { return }
    
                for items in array {
                    print(items)
    
                }
    
    }
    
    func loadJson(fileName: String) -> ResponseData? {
        // if let url = Bundle.main.url(forResource: fileName, withExtension: "json") {
        if let url = URL(string:"file://" + pathFile) {
            // print("-----------")
            // print(url)
            do {
                let data = try Data(contentsOf: url)
                let decoder = JSONDecoder()
                let jsonData = try decoder.decode(ResponseData.self, from: data)
    
                return jsonData
            } catch {
                print("error:\(error)")
            }
        }
        return nil
    }
    

    et ResponseData :

    import Foundation
    
    struct ResponseData: Codable {
    
    let champ1: String
        let champ2: String
        let time: JSONNull?
    let detailsData: [[DetailsDatum]]
    }
    
    enum DetailsDatum: Codable {
    case bool(Bool)
    case double(Double)
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let x = try? container.decode(Bool.self) {
            self = .bool(x)
            return
        }
        if let x = try? container.decode(Double.self) {
            self = .double(x)
            return
        }
        throw DecodingError.typeMismatch(DetailsDatum.self, DecodingError.Context(codingPath: 
      decoder.codingPath, debugDescription: "Wrong type for DetailsDatum"))
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .bool(let x):
            try container.encode(x)
        case .double(let x):
            try container.encode(x)
        }
      }
     }
    
     // MARK: Encode/decode helpers
    
    class JSONNull: Codable {
    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()
     }
    }
    

    le code suivant qui est un test :

     guard let array = resultData?.detailsData[1] else { return }
    
            for items in array {
                print(items)
    
            }
    

    affiche les
    double(0)
    bool(true)
    double(1)
    bool(true)
    double(0)
    bool(false)

  • heliohelio Membre
    juillet 2018 modifié #10

    Pas de solution pour afficher uniquement les valeurs 0, true, 1, true, 0, false et non double(0), bool(true) etc... ?

  • Pour une raison que je ne comprends pas (et j'ai pas besoin de comprendre) tu mets tes résultats dans les membres d'un enum. Qui par défaut s'affiche comme ce que tu vois. Tu aurais rajouté un case string(String) tu y verrai aussi des choses du genre string(Coucou).

    Bref pour éviter ça fais en sorte que DetailsDatum se conforme au protocol CustomStringConvertible. Lis la doc pour comprendre.

  • Joanna CarterJoanna Carter Membre, Modérateur

    Petit exemple :

    struct Welcome: Codable
    {
      let greeting: String
      let instructions: [String]
    }
    
    
    @NSApplicationMain
    class AppDelegate: NSObject, NSApplicationDelegate
    {
      func applicationDidFinishLaunching(_ aNotification: Notification)
      {
        let welcome = Welcome(greeting: "Hello", instructions: ["Line1", "Line2", "Line3"])
    
        let encoder = JSONEncoder()
    
        do
        {
          let data = try encoder.encode(welcome)
    
          let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("Messages.json")
    
          if FileManager.default.fileExists(atPath: url.path)
          {
            try FileManager.default.removeItem(at: url)
          }
    
          FileManager.default.createFile(atPath: url.path, contents: data, attributes: nil)
        }
        catch
        {
          fatalError(error.localizedDescription)
        }
    
        let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("Messages.json")
    
        if let data = FileManager.default.contents(atPath: url.path)
        {
          let decoder = JSONDecoder()
    
          do
          {
            let welcome = try decoder.decode(Welcome.self, from: data)
    
            for instruction in welcome.instructions
            {
              print(instruction)
            }
          }
          catch
          {
            fatalError(error.localizedDescription)
          }
    
        }
        else
        {
          fatalError("No data at \(url.path)!")
        }
      }
    }
    
  • Merci pour vos aides respectives, j'ai ce que je veux avec ce code :

    struct DetailsDatum: Codable {
    var value:String
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
    
        if let x = try? container.decode(Bool.self) {
            self.value = String(describing: x)
            return
        }
    
        if let x = try? container.decode(Double.self) {
            self.value = String(describing: x)
            return
        }
        throw DecodingError.typeMismatch(DetailsDatum.self, DecodingError.Context(codingPath:
            decoder.codingPath, debugDescription: "Wrong type for DetailsDatum"))
    }
    
    func encode(to encoder: Encoder) throws {
    
    }
    
    }
    

    c'est l'outil de Pyroh https://app.quicktype.io/#l=swift qui m'avait généré un enum mais dans ce cas ce n'est pas la peine.

  • Joanna CarterJoanna Carter Membre, Modérateur
    juillet 2018 modifié #14

    ????? Pourquoi tout ce code ? Codable, c'est automatique sur les structs comme ça !

    Et pourquoi transformer les valeurs en String là ?

    Pour les afficher dans une tableView, tu fais la transformation lorsque tu les mets dans le tableView, pas pendant le décodage

  • Joanna CarterJoanna Carter Membre, Modérateur
    juillet 2018 modifié #15

    Et pour la preuve que tu fais le code trop compliqué :

    struct Objet: Codable
    {
      struct Donnee : Codable
      {
        let intValue: Int
    
        let boolValue: Bool
      }
    
      let champ1: String
    
      let champ2: String
    
      let entete: [String]
    
      let donnees: [Donnee]
    
      let champ3: String
    }
    
    
    @NSApplicationMain
    class AppDelegate: NSObject, NSApplicationDelegate
    {
      func applicationDidFinishLaunching(_ aNotification: Notification)
      {
        let objet = Objet(champ1: "Test 1", champ2: "Test 2", entete: ["valeur 1", "valeur 2"], donnees: [Objet.Donnee(intValue: 0, boolValue: true), Objet.Donnee(intValue: 1, boolValue: true), Objet.Donnee(intValue: 0, boolValue: false)], champ3: "valeur 3")
    
        let encoder = JSONEncoder()
    
        do
        {
          let data = try encoder.encode(objet)
    
          let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("Objets.json")
    
          if FileManager.default.fileExists(atPath: url.path)
          {
            try FileManager.default.removeItem(at: url)
          }
    
          FileManager.default.createFile(atPath: url.path, contents: data, attributes: nil)
        }
        catch
        {
          fatalError(error.localizedDescription)
        }
    
        let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("Objets.json")
    
        if let data = FileManager.default.contents(atPath: url.path)
        {
          let decoder = JSONDecoder()
    
          do
          {
            let objet = try decoder.decode(Objet.self, from: data)
    
            for donnee in objet.donnees
            {
              print("\(donnee.intValue) : \(donnee.boolValue)")
            }
          }
          catch
          {
            fatalError(error.localizedDescription)
          }
    
        }
        else
        {
          fatalError("No data at \(url.path)!")
        }
      }
    }
    

    Et le contenu de Objets.json :

    {"champ3":"valeur 3","champ1":"Test 1","entete":["valeur 1","valeur 2"],"champ2":"Test 2","donnees":[{"intValue":0,"boolValue":true},{"intValue":1,"boolValue":true},{"intValue":0,"boolValue":false}]}
    
  • Merci Joanna, je vais étudier ton code.

Connectez-vous ou Inscrivez-vous pour répondre.