可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have a code, that parses JSON's list of questions and I can get every property. How can I iterate through the whole file and for each question create an object ?
class ViewController: UIViewController { var hoge: JSON? override func viewDidLoad() { super.viewDidLoad() let number = arc4random_uniform(1000) let url = NSURL(string: "http://www.wirehead.ru/try-en.json?\(number)") var request = NSURLRequest(URL: url!) var data = NSURLConnection.sendSynchronousRequest(request, returningResponse: nil, error: nil) if data != nil { hoge = JSON(data: data!) let level = hoge!["pack1"][0]["level"].intValue let questionText = hoge!["pack1"][0]["questionText"].stringValue let answer1 = hoge!["pack1"][0]["answer1"].stringValue let answer2 = hoge!["pack1"][0]["answer2"].stringValue let answer3 = hoge!["pack1"][0]["answer3"].stringValue let answer4 = hoge!["pack1"][0]["answer4"].stringValue let correctAnswer = hoge!["pack1"][0]["correctAnswer"].stringValue let haveAnswered = hoge!["pack1"][0]["haveAnswered"].boolValue } } }
my model of Question which objects I want to create below
class Question { var level : Int? var questionText : String? var answer1 : String? var answer2 : String? var answer3 : String? var answer4 : String? var correctAnswer : String? var haveAnswered : Bool = false init(level: Int, questionText:String, answer1:String, answer2:String, answer3:String, answer4:String, correctAnswer: String, haveAnswered:Bool) { self.level = level self.questionText = questionText self.answer1 = answer1 self.answer2 = answer2 self.answer3 = answer3 self.answer4 = answer4 self.correctAnswer = correctAnswer self.haveAnswered = false } }
回答1:
This is how I would approach the problem.
Step 1
Since your init
inside Question
does receive non optional
objects, I had the feeling that the properties of Questions should be non optional too. I also converted the properties from var
to let
(tell me if I am wrong).
Step 2
This is the refactored Question
class. As you can see I added a class method build
that receive a JSON
(a SwiftyJSON
) and returns a Question
(if the json contains correct data), nil otherwise.
Right now I cannot do this with a failable initializer
.
extension String { func toBool() -> Bool? { switch self.lowercaseString { case "true", "1", "yes" : return true case "false", "0", "no" : return false default: return nil } } } class Question { let level: Int let questionText: String let answer1: String let answer2: String let answer3: String let answer4: String let correctAnswer: String let haveAnswered: Bool init(level: Int, questionText:String, answer1:String, answer2:String, answer3:String, answer4:String, correctAnswer: String, haveAnswered:Bool) { self.level = level self.questionText = questionText self.answer1 = answer1 self.answer2 = answer2 self.answer3 = answer3 self.answer4 = answer4 self.correctAnswer = correctAnswer self.haveAnswered = false } class func build(json:JSON) -> Question? { if let level = json["level"].string?.toInt(), questionText = json["questionText"].string, answer1 = json["answer1"].string, answer2 = json["answer2"].string, answer3 = json["answer3"].string, answer4 = json["answer4"].string, correctAnswer = json["correctAnswer"].string, haveAnswered = json["haveAnswered"].string?.toBool() { return Question( level: level, questionText: questionText, answer1: answer1, answer2: answer2, answer3: answer3, answer4: answer4, correctAnswer: correctAnswer, haveAnswered: haveAnswered) } else { debugPrintln("bad json \(json)") return nil } } }
Step 3
Now let's look at viewDidLoad
.
func viewDidLoad() { super.viewDidLoad() let number = arc4random_uniform(1000) if let url = NSURL(string: "http://www.wirehead.ru/try-en.json?\(number)"), data = NSURLConnection.sendSynchronousRequest(NSURLRequest(URL: url), returningResponse: nil, error: nil) { // line #a let rootJSON = JSON(data: data) // line #b if let questions = (rootJSON["pack1"].array?.map { return Question.build($0) }) { // now you have an array of optional questions [Question?]... } } }
At line #a I put inside rootJSON
the whole data received from the connection (converted into JSON
).
What happen at line #b?
Well I try to access the array located inside "pack1"
.
rootJSON["pack1"].array?
If the array exists I run the map method. This will extract each cell of the array and I will be able to refer to it with the $0
parameter name inside the closure.
Inside the closure I use this json block (that should represent a question) to build a Question
instance.
The result will be an array of Question?
. There could be ill values if some son data was not valid. If you want I can show you how to remove the nil
values from this array
I could not try the code with real data, hope this helps.
回答2:
Step 1. We will create one protocol with one constructor method in it and Model class
protocol JSONable { init?(parameter: JSON) } class Style: JSONable { let ID :String! let name :String! required init(parameter: JSON) { ID = parameter["id"].stringValue name = parameter["name"].stringValue } /* JSON response format { "status": true, "message": "", "data": [ { "id": 1, "name": "Style 1" }, { "id": 2, "name": "Style 2" }, { "id": 3, "name": "Style 3" } ] } */ }
Step 2. We will create extension of JSON which will convert JSON to model class type object
extension JSON { func to<T>(type: T?) -> Any? { if let baseObj = type as? JSONable.Type { if self.type == .array { var arrObject: [Any] = [] for obj in self.arrayValue { let object = baseObj.init(parameter: obj) arrObject.append(object!) } return arrObject } else { let object = baseObj.init(parameter: self) return object! } } return nil } }
Step 3. Use code with Alamofire or other code.
Alamofire.request(.GET, url).validate().responseJSON { response in switch response.result { case .success(let value): let json = JSON(value) var styles: [Style] = [] if let styleArr = json["data"].to(type: Style.self) { styles = styleArr as! [Style] } print("styles: \(styles)") case .failure(let error): print(error) } }
I hope this will be useful.
Please refer to this link for more information on this.
https://github.com/SwiftyJSON/SwiftyJSON/issues/714
回答3:
You can use SwiftyJSONModel which was specifically designed for this purpose. So in your case the model would be like this:
class Question: JSONObjectInitializable { enum PropertyKey: String { case level, questionText case answer1, answer2, answer3, answer4 case correctAnswer, haveAnswered } var level : Int? var questionText : String? var answer1 : String? var answer2 : String? var answer3 : String? var answer4 : String? var correctAnswer : String? var haveAnswered : Bool = false required init(object: JSONObject<PropertyKey>) throws { level = object.value(for: .level) questionText = object.value(for: .questionText) answer1 = object.value(for: .answer1) answer2 = object.value(for: .answer2) answer3 = object.value(for: .answer3) answer4 = object.value(for: .answer4) correctAnswer = object.value(for: .correctAnswer) haveAnswered = object.value(for: .haveAnswered) ?? false } }
And then do like this:
let rootJSON = JSON(data: data) let questions = rootJSON.arrayValue.flatMap { try? Question(json: $0) }
The framework gives you several nice features:
- All the keys are stored in separated enum
PropertyKey
- No boilerplate as
stringValue
, intValue
etc. - If
JSON
will be invalid, framework will give a verbose error and you will immediately see what exactly went wrong
回答4:
for (item, content) in hoge { let level = content["level"].intValue }
that should work