如何从SwiftyJSON创建对象

17

我有一段代码,可以解析JSON中的问题列表并获取每个属性。如何遍历整个文件,并为每个问题创建一个对象?

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

    }
  }
}

我的问题模型是在下面创建哪些对象。

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
    }

}

我看到你将除了 haveAnswered 以外的属性声明为 optional。另一方面,你的 init 的所有参数都是 not optionals。哪一个是正确的? - Luca Angeletti
4个回答

13

这是我解决问题的方法。

步骤1

由于在 Question 类中,init 接收的是非可选对象,我觉得 Questions 的属性也应该是非可选的。此外,我还将属性从 var 转换为 let(如果我错了,请告诉我)。

步骤2

这是重新编写的 Question 类。正如您所看到的,我添加了一个类方法 build,该方法接收一个 JSON(使用 SwiftyJSON)并返回一个 Question(如果 json 包含正确的数据),否则返回 nil。

目前我不能使用可失败的初始化程序来完成这个操作。

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
        }
    }
}

第三步

现在让我们来看一下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?]...
        }

    }
}

在第#a行,我将从连接中接收到的所有数据(转换为JSON格式)放入了rootJSON中。

在第#b行发生了什么?

嗯,我试图访问位于"pack1"内部的数组。

rootJSON["pack1"].array?
如果数组存在,我运行map方法。这将提取数组的每个单元格,并且我可以在闭包内使用$0参数名称来引用它。
在闭包内,我使用此json块(应该表示一个问题)来构建Question实例。
结果将是一个Question?数组。如果某些子数据无效,则可能会有错误值。如果您想,我可以向您展示如何从此数组中删除nil值。
我无法使用真实数据尝试代码,希望这有所帮助。

3
非常好的回答。我正在建模一个类似的项目,这真的对我很有帮助。 - damianesteban
非常感谢!不幸的是,我遇到了糟糕的JSON错误。我检查了JSON,它通过了验证。您能否帮忙看看出了什么问题?数据在这里http://www.wirehead.ru/try-en.json - Alexey K
相同的结果,错误的 JSON :( - Alexey K
请注意,在原始的JSON中,“answer2”被拼错了4次(写成“asnwer2”而不是“answer2”)。 - Luca Angeletti
哦,对不起打错了。现在它运行得很好!非常感谢你!! - Alexey K
显示剩余3条评论

9
第一步,我们将创建一个协议并在其中添加一个构造方法和模型类。
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"
        }
      ]
    }
    */
}

第二步。我们将创建JSON的扩展,将JSON转换为模型类类型对象。

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
    }
}

第三步。使用Alamofire或其他代码。
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)
        }
 }

我希望这对你有所帮助。

更多相关信息请参考此链接:
https://github.com/SwiftyJSON/SwiftyJSON/issues/714


1
这个答案非常棒。在没有做这个的情况下,我一直遇到内存泄漏问题,而这实际上是唯一帮助我的解决方案!谢谢! - JuicyFruit

9
您可以使用专门为此目的设计的SwiftyJSONModel。因此,在您的情况下,模型应如下所示:
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
    }   
}

然后像这样做:

let rootJSON = JSON(data: data)
let questions = rootJSON.arrayValue.flatMap { try? Question(json: $0) }

该框架为您带来了几个不错的功能:
  1. 所有的键都存储在单独的枚举类 PropertyKey 中。
  2. 没有像 stringValueintValue 等样的样板代码。
  3. 如果JSON无效,该框架将提供详细的错误信息,您将立即知道出了什么问题。

1
for (item, content) in hoge {
    let level = content["level"].intValue
}

那应该可以工作


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接