web-dev-qa-db-fra.com

Désérialiser JSON / NSDictionary en Swift

Existe-t-il un moyen de désérialiser correctement une réponse JSON en Swift ou en utilisant des DTO comme conteneurs pour des API JSON fixes?)?

Quelque chose de semblable à http://james.newtonking.com/json ou quelque chose comme cet exemple de Java

User user = jsonResponse.readEntity(User.class);

jsonResponse.toString() est quelque chose comme

{
  "name": "myUser", 
  "email": "[email protected]",
  "password": "passwordHash"
}
52
dimitri

Mise à jour Swift 4


Puisque vous donnez à un objet JSON très simple le code préparé pour gérer ce modèle. Si vous avez besoin de modèles JSON plus complexes, vous devez améliorer cet exemple.

Votre objet personnalisé

class Person : NSObject {
    var name : String = ""
    var email : String = ""
    var password : String = ""

    init(JSONString: String) {
        super.init()

        var error : NSError?
        let JSONData = JSONString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)

        let JSONDictionary: Dictionary = NSJSONSerialization.JSONObjectWithData(JSONData, options: nil, error: &error) as NSDictionary

        // Loop
        for (key, value) in JSONDictionary {
            let keyName = key as String
            let keyValue: String = value as String

            // If property exists
            if (self.respondsToSelector(NSSelectorFromString(keyName))) {
                self.setValue(keyValue, forKey: keyName)
            }
        }
        // Or you can do it with using 
        // self.setValuesForKeysWithDictionary(JSONDictionary)
        // instead of loop method above
    }
}

Et voici comment vous appelez votre classe personnalisée avec une chaîne JSON.

override func viewDidLoad() {
    super.viewDidLoad()
    let jsonString = "{ \"name\":\"myUser\", \"email\":\"[email protected]\", \"password\":\"passwordHash\" }"
    var aPerson : Person = Person(JSONString: jsonString)
    println(aPerson.name) // Output is "myUser"
}
60
mohacs

Je vous recommande d'utiliser la génération de code ( http://www.json4Swift.com ) pour créer des modèles natifs à partir de la réponse JSON, cela vous fera gagner du temps d'analyse manuelle et réduira le risque d'erreur. en raison de fausses clés, tous les éléments seront accessibles par les propriétés du modèle, ce sera purement natif et les modèles auront plus de sens plutôt que de vérifier les clés.

Votre conversion sera aussi simple que:

let userObject = UserClass(userDictionary)
print(userObject!.name)
10
Syed Absar

Swift 2: J'aime beaucoup le post précédent de Mohacs! Pour le rendre plus orienté objet, j'ai écrit une extension correspondante:

extension NSObject{       
    convenience init(jsonStr:String) {            
        self.init()

        if let jsonData = jsonStr.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
        {
            do {
                let json = try NSJSONSerialization.JSONObjectWithData(jsonData, options: []) as! [String: AnyObject]

                // Loop
                for (key, value) in json {
                    let keyName = key as String
                    let keyValue: String = value as! String

                    // If property exists
                    if (self.respondsToSelector(NSSelectorFromString(keyName))) {
                        self.setValue(keyValue, forKey: keyName)
                    }
                }

            } catch let error as NSError {
                print("Failed to load: \(error.localizedDescription)")
            }
        }
        else
        {
            print("json is of wrong format!")
        }
    }
}

classes personnalisées:

class Person : NSObject {
       var name : String?
       var email : String?
       var password : String?
}

class Address : NSObject {
       var city : String?
       var Zip : String?
}

appel de classes personnalisées avec une chaîne JSON:

var jsonString = "{ \"name\":\"myUser\", \"email\":\"[email protected]\", \"password\":\"passwordHash\" }"
let aPerson = Person(jsonStr: jsonString)
print(aPerson.name!) // Output is "myUser"

jsonString = "{ \"city\":\"Berlin\", \"Zip\":\"12345\" }"
let aAddress = Address(jsonStr: jsonString)
print(aAddress.city!) // Output is "Berlin"
9
Peter Kreinz

Encore un autre gestionnaire JSON que j'ai écrit:

Avec ça tu peux aller comme ça:

let obj:[String:AnyObject] = [
    "array": [JSON.null, false, 0, "", [], [:]],
    "object":[
        "null":   JSON.null,
        "bool":   true,
        "int":    42,
        "double": 3.141592653589793,
        "string": "a α\t弾\n????",
        "array":  [],
        "object": [:]
    ],
    "url":"http://blog.livedoor.com/dankogai/"
]

let json = JSON(obj)

json.toString()
json["object"]["null"].asNull       // NSNull()
json["object"]["bool"].asBool       // true
json["object"]["int"].asInt         // 42
json["object"]["double"].asDouble   // 3.141592653589793
json["object"]["string"].asString   // "a α\t弾\n????"
json["array"][0].asNull             // NSNull()
json["array"][1].asBool             // false
json["array"][2].asInt              // 0
json["array"][3].asString           // ""

Comme vous le voyez, aucun !? N'est nécessaire entre les indices.

En plus de cela, vous pouvez appliquer votre propre schéma comme ceci:

//// schema by subclassing
class MyJSON : JSON {
    override init(_ obj:AnyObject){ super.init(obj) }
    override init(_ json:JSON)  { super.init(json) }
    var null  :NSNull? { return self["null"].asNull }
    var bool  :Bool?   { return self["bool"].asBool }
    var int   :Int?    { return self["int"].asInt }
    var double:Double? { return self["double"].asDouble }
    var string:String? { return self["string"].asString }
    var url:   String? { return self["url"].asString }
    var array :MyJSON  { return MyJSON(self["array"])  }
    var object:MyJSON  { return MyJSON(self["object"]) }
}

let myjson = MyJSON(obj)
myjson.object.null      // NSNull?
myjson.object.bool      // Bool?
myjson.object.int       // Int?
myjson.object.double    // Double?
myjson.object.string    // String?
myjson.url              // String?
7
dankogai

Il y a un bon exemple par Apple pour désérialiser JSON avec Swift 2.0

L'astuce consiste à utiliser le mot clé guard et à enchaîner les assignations de la manière suivante:

init?(attributes: [String : AnyObject]) {
    guard let name = attributes["name"] as? String,
        let coordinates = attributes["coordinates"] as? [String: Double],
        let latitude = coordinates["lat"],
        let longitude = coordinates["lng"],
        else {
            return nil
    }
    self.name = name
    self.coordinates = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
}

Personnellement, je préfère l’analyse native à une tierce partie, car elle est transparente et sans magie. (et bug moins?)

4
AmitP

Si vous souhaitez analyser de et vers json sans avoir à mapper manuellement les clés et les champs, vous pouvez également utiliser EVReflection . Vous pouvez ensuite utiliser un code comme:

var user:User = User(json:jsonString)

ou

var jsonString:String = user.toJsonString()

La seule chose à faire est d'utiliser EVObject comme classe de base de vos objets de données. Voir la page GitHub pour un exemple de code plus détaillé

3
Edwin Vermeer

J'ai récemment écrit cette petite bibliothèque open-source qui vous permet de désérialiser rapidement et facilement les dictionnaires en Swift objets: https://github.com/isair/JSONHelper

En l’utilisant, la désérialisation des données devient aussi simple que cela:

var myInstance = MyClass(data: jsonDictionary)

ou

myInstance <-- jsonDictionary

Et les modèles doivent ressembler à ceci:

struct SomeObjectType: Deserializable {
    var someProperty: Int?
    var someOtherProperty: AnotherObjectType?
    var yetAnotherProperty: [YetAnotherObjectType]?

    init(data: [String: AnyObject]) {
        someProperty <-- data["some_key"]
        someOtherProperty <-- data["some_other_key"]
        yetAnotherProperty <-- data["yet_another_key"]
    }
}

Ce qui, dans votre cas, serait:

struct Person: Deserializable {
    var name: String?
    var email: String?
    var password: String?

    init(data: [String: AnyObject]) {
        name <-- data["name"]
        email <-- data["email"]
        password <-- data["password"]
    }
}
3
isair

En utilisant quicktype , j'ai généré vos assistants de modèle et de sérialisation à partir de votre exemple:

import Foundation

struct User: Codable {
    let name: String
    let email: String
    let password: String
}

extension User {
    static func from(json: String, using encoding: String.Encoding = .utf8) -> OtherUser? {
        guard let data = json.data(using: encoding) else { return nil }
        return OtherUser.from(data: data)
    }

    static func from(data: Data) -> OtherUser? {
        let decoder = JSONDecoder()
        return try? decoder.decode(OtherUser.self, from: data)
    }

    var jsonData: Data? {
        let encoder = JSONEncoder()
        return try? encoder.encode(self)
    }

    var jsonString: String? {
        guard let data = self.jsonData else { return nil }
        return String(data: data, encoding: .utf8)
    }
}

Ensuite, analysez User comme ceci:

let user = User.from(json: """{
  "name": "myUser", 
  "email": "[email protected]",
  "password": "passwordHash"
}""")!
3
David Siegel

HandyJSON est une autre option permettant de gérer JSON pour vous. https://github.com/alibaba/handyjson

Il désériorise JSON à objecter directement. Pas besoin de spécifier la relation de mappage, pas besoin d'hériter de NSObject. Définissez simplement votre classe/structure pure-Swift et votre JSON deserial.

 classe Animal: HandyJSON {
 nom de la variable: String? 
 var id: Chaîne? 
 var num: Int? 
 
 required init () {} 
} 
 
 let jsonString = "{\" name\": \" cat\", \" id\": \" 12345\" ,\"num \": 180} "
 
 si animal = JSONDeserializer.deserializeFrom (jsonString) {
 empreinte (animal) 
} 
3
xuyecan

Je développe un peu les excellentes réponses de Mohacs et Peter Kreinz pour couvrir le tableau d'objets similaires, chaque objet contenant un mélange de types de données JSON valides. Si les données JSON analysées sont un tableau d'objets similaires contenant un mélange de types de données JSON, la boucle d'analyse des données JSON devient la suivante.

// Array of parsed objects
var parsedObjects = [ParsedObject]()
do {
    let json = try NSJSONSerialization.JSONObjectWithData(jsonData, options: []) as [Dictionary<String, AnyObject>]
    // Loop through objects
    for dict in json {
        // ParsedObject is a single instance of an object inside the JSON data
        // Its properties are a mixture of String, Int, Double and Bool
        let parsedObject = ParsedObject()
        // Loop through key/values in object parsed from JSON
        for (key, value) in json {
            // If property exists, set the value
            if (parsedObject.respondsToSelector(NSSelectorFromString(keyName))) {
                // setValue can handle AnyObject when assigning property value
                parsedObject.setValue(keyValue, forKey: keyName)
            }
        }
        parsedObjects.append(parsedObject)
    }
} catch let error as NSError {
    print("Failed to load: \(error.localizedDescription)")
}
2
jkwuc89

Dans Swift 4, vous pouvez utiliser les protocoles de décodage , pour désérialiser la réponse JSON:

  1. Créer la classe qui confirme le protocole décodable

    class UserInfo: Decodable

  2. Créer des membres de la classe

    var name: String

    var email: String

    var password: String

  3. Créer une énumération de clé JSON qui hérite de CodingKey

    enum UserInfoCodingKey: String, CodingKey { case name case password case emailId }

  4. Implémenter init

    required init(from decoder: Decoder) throws

    Toute la classe ressemble à:

    enter image description here

  5. Appel décodeur

    // jsonData est une réponse JSON et nous obtenons l'objet userInfo

    let userInfo = try JsonDecoder().decode(UserInfo.self, from: jsonData)

2
Yogesh Bharate

De cette façon, vous obtenez l'utilisateur à partir d'une URL. Il analyse le NSData dans un NSDictionary puis dans votre NSObject.

let urlS = "http://api.localhost:3000/"

func getUser(username: Strung) -> User {
   var user = User()
   let url = NSURL(string: "\(urlS)\(username)")
   if let data = NSData(contentsOfURL: url!) {
     setKeysAndValues(user, dictionary: parseData(data))
   }
   return user
}

func setKeysAndValues (object : AnyObject, dictionary : NSDictionary)  -> AnyObject  {
    for (key, value) in dictionary {
        if let key = key  as? String, let value = value as? String {
            if (object.respondsToSelector(NSSelectorFromString(key))) {
                object.setValue(value, forKey: key)
            }
        }
    }
    return object
}

func parseData (data : NSData)  -> NSDictionary  {
    var error: NSError?
    return NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &error) as! NSDictionary
}
1
Eduardo Irias

Vous faites cela en utilisant NSJSONSerialization . Où data est votre JSON.

Commencez par l'envelopper dans une instruction if pour fournir une capacité de traitement des erreurs

if let data = data,
 json = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? [String: AnyObject] {
// Do stuff
} else {
  // Do stuff
  print("No Data :/")
}

puis leur assigner:

let email = json["email"] as? String
let name = json["name"] as? String
let password = json["password"] as? String

Maintenant, cela va vous montrer le résultat:

print("Found User iname: \(name) with email: \(email) and pass \(password)")

Tiré de ce Swift Parse JSON tutoriel. Vous devriez consulter le tutoriel, car il va beaucoup plus en profondeur et couvre une meilleure gestion des erreurs.

0
MarkP