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);
où jsonResponse.toString()
est quelque chose comme
{
"name": "myUser",
"email": "[email protected]",
"password": "passwordHash"
}
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"
}
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)
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"
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?
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?)
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é
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"]
}
}
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"
}""")!
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) }
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)")
}
Dans Swift 4, vous pouvez utiliser les protocoles de décodage , pour désérialiser la réponse JSON:
Créer la classe qui confirme le protocole décodable
class UserInfo: Decodable
Créer des membres de la classe
var name: String
var email: String
var password: String
Créer une énumération de clé JSON qui hérite de CodingKey
enum UserInfoCodingKey: String, CodingKey { case name case password case emailId }
Implémenter init
required init(from decoder: Decoder) throws
Toute la classe ressemble à:
Appel décodeur
// jsonData est une réponse JSON et nous obtenons l'objet userInfo
let userInfo = try JsonDecoder().decode(UserInfo.self, from: jsonData)
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
}
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.