How to use generics and inheritance to parse an output? - swift

I'm programming a network call for a backend in NestJs which has pagination enabled. The output of the service has the following structure:
{
"items": [{
"id": "26c26ecb-0763-4813-b489-3c6ad94084a9",
"body": "Test body",
"subTitle": "SubTitle Test",
"author": "Test Author",
"createAt": "2023-01-12T07:04:58.480Z",
"title": "Temporary Title",
"updateAt": "2023-01-12T07:04:58.480Z"
}],
"meta": {
"itemsPerPage": 10,
"totalPages": 2,
"currentPage": 1,
"totalItems": 19,
"itemCount": 10
},
"links": {
"previous": "",
"first": "\/post\/public?limit=10",
"next": "\/post\/public?page=2&limit=10",
"last": "\/post\/public?page=2&limit=10"
}
}
The Meta and Links atribute are fairly straight through so they have no problems. The issue that i'm having is the items list. The objects data structures that are inside can be different, there can be News, Posts, Events.
All of this objects have some properties in common so a BaseEntity Model was created. Therefore all models inherit from BaseEntity.
I dont want to create a class for each of this outputs. I want to declare a generic class that receives a generic an parses it accordingly.
My BaseCrudEntity model and protocol are designed like this:
public protocol BaseCrudEntityParsingProtocol: AnyObject
{
var id: String { get set }
var createAt: Date { get set }
var updateAt: Date { get set }
var deletedAt: Date? { get set }
var isActive: Bool { get set }
init()
init?(info: Any?)
static func parseList(info: Any?) -> [BaseCrudEntityParsingProtocol]?
}
public extension BaseCrudEntityParsingProtocol
{
static func parseList(info: Any?) -> [BaseCrudEntityParsingProtocol]? {
return nil
}
}
public class BaseCrudEntity: BaseCrudEntityParsingProtocol
{
public var createAt: Date = Date()
public var updateAt: Date = Date()
public var deletedAt: Date?
public var isActive: Bool = true
public var id: String = ""
enum CodingKeysBase: String {
case createAt = "createAt"
case updateAt = "updateAt"
case deletedAt = "deletedAt"
case isActive = "isActive"
case id = "id"
}
required public init() { }
required public init?(info: Any?) {
guard let anyInfo = info,
let info = anyInfo as? NSDictionary else { return nil }
self.createAt = info.parseString(forKeyPath: CodingKeysBase.createAt.rawValue).toDate()
self.updateAt = info.parseString(forKeyPath: CodingKeysBase.updateAt.rawValue).toDate()
self.deletedAt = info.parseStringOptional(forKeyPath: CodingKeysBase.deletedAt.rawValue)?.toDateOptional()
self.isActive = info.parseBool(forKeyPath: CodingKeysBase.isActive.rawValue)
self.id = info.parseString(forKeyPath: CodingKeysBase.id.rawValue)
}
}
And an example of a specific model which will have pagination looks like this:
// MARK: - Post
public class Post: BaseCrudEntity
{
public var title: String?
public var subTitle: String?
public var author: String?
public var body: String?
enum CodingKeys: String, CodingKey {
case title = "title"
case subTitle = "subTitle"
case author = "author"
case body = "body"
}
required public init() {
super.init()
}
required public init?(info: Any?) {
super.init(info: info)
guard let anyInfo = info,
let info = anyInfo as? NSDictionary else { return nil }
self.title = info.parseStringOptional(forKeyPath: CodingKeys.title.rawValue)
self.subTitle = info.parseStringOptional(forKeyPath: CodingKeys.subTitle.rawValue)
self.author = info.parseStringOptional(forKeyPath: CodingKeys.author.rawValue)
self.body = info.parseStringOptional(forKeyPath: CodingKeys.body.rawValue)
}
public static func parseList(info: Any?) -> [Post]? {
guard let anyInfo = info,
let rawList = anyInfo as? [NSDictionary] else { return nil }
return rawList.compactMap(Post.init)
}
}
I've created the following class so that I can pass a generic value and parse it accordingly, the problem that i'm having is that the items list returns nil, as if the parsing cannot be done successfully or is having problems. This task could be done using the Codable protocol but due to the codebase design it cannot be done at this time.
public class PaginatedList<Body: BaseCrudEntity> where Body: BaseCrudEntity
{
public var items: [Body]?
public var meta: PaginatedListMeta?
public var links: PaginatedListLinks?
enum CodingKeys: String, CodingKey {
case meta = "meta"
case links = "links"
case items = "items"
}
public init() {
}
public init(items: [Body]?, meta: PaginatedListMeta?, links: PaginatedListLinks?) {
self.meta = meta
self.links = links
}
public init?(info: Any?) {
guard let anyInfo = info,
let info = anyInfo as? NSDictionary else { return nil }
self.meta = PaginatedListMeta(info: info[CodingKeys.meta.rawValue])
self.links = PaginatedListLinks(info: info[CodingKeys.links.rawValue])
self.items = Body.parseList(info: info[CodingKeys.items.rawValue]) as? [Body]
}
}
I've tried a couple of solutions but none seem to work.

Related

[Core Data]: data: <fault>

The questions parameter in the function is full, but I cannot transfer assignments to questionsCD.questions?.questionList.
The id and title variables are not nil. Those variables are working in a healthy way.
In Console output, questions return nil.
I cannot assign the questionList variable in the QuestionsNSSecureCoding class, so I think it returns nil. Why ?
You can examine the Core Data Entity image to see the questionsCD.questions?.questionList in more detail.
Core Data Save Function:
func saveSelectedQuestion(questionTitle: String, id: String, questions: [QuestionList]) {
let questionsCD = QuestionCD(context: persistentContainer.viewContext)
questionsCD.title = questionTitle
questionsCD.id = id
questionsCD.questions?.questionList = questions
print("nil test: \(questionsCD.questions?.questionList ?? [])")
do {
try persistentContainer.viewContext.save()
} catch let error {
print("Failed to save selected category: \(error.localizedDescription)")
}
}
Core Data Get Function:
func getSelectedQuestion(questionID: String) -> [QuestionCD] {
let fetchRequest: NSFetchRequest<QuestionCD> = QuestionCD.fetchRequest()
let search = NSPredicate(format: "id == %#", questionID)
print("search: \(search)")
fetchRequest.predicate = search
fetchRequest.returnsObjectsAsFaults = false
print("request predicate: \(String(describing: fetchRequest.predicate))")
do {
return try persistentContainer.viewContext.fetch(fetchRequest)
} catch let error {
print("get hata: \(error.localizedDescription)")
return []
}
}
Console Output:
selectedQuestionCD [<QuestionCD: 0x2800e8e60> (entity: QuestionCD; id: 0x9d00d28ec7359eb0 <x-coredata://089E80AC-0E4F-4303-BF8F-47C31EC70ED4/QuestionCD/p2>; data: {
id = "agustos_test_1";
questions = nil;
title = "A\U011fustos Test 1";
})]
Core Data Entity:
Questions NSSecure Coding:
public class QuestionsNSSecureCoding: NSObject, NSSecureCoding {
public static var supportsSecureCoding: Bool = true
var questionList: [QuestionList]
required init(questions: [QuestionList]) {
self.questionList = questions
}
public func encode(with coder: NSCoder) {
coder.encode(questionList, forKey: "questionList")
}
required public init?(coder: NSCoder) {
questionList = coder.decodeObject(of: NSArray.self, forKey: "questionList") as? Array<QuestionList> ?? []
}
}
Questions Value Transformer:
#objc(QuestionsValueTransformer)
final class QuestionsValueTransformer: NSSecureUnarchiveFromDataTransformer {
static let name = NSValueTransformerName(rawValue: String(describing: QuestionsValueTransformer.self))
override static var allowedTopLevelClasses: [AnyClass] {
return [QuestionsNSSecureCoding.self]
}
public static func register() {
let transformer = QuestionsValueTransformer()
ValueTransformer.setValueTransformer(transformer, forName: name)
}
}
My Custom Model:
class QuestionContainer: Codable {
var questionCategories: [Question]
init(questionCategories: [Question]) {
self.questionCategories = questionCategories
}
}
class Question: Codable, Identifiable {
var title: String
var id: String
var questions: [QuestionList]
init(title: String, id: String, questions: [QuestionList]) {
self.title = title
self.id = id
self.questions = questions
}
}
class QuestionList: Codable, Identifiable {
var id: String
init(id: String) {
self.id = id
}
}
I think the problem is that questionCD.questions is nil, so the line
questionsCD.questions?.questionList = questions
does nothing. You need to create an instance of QuestionsNSSecureCoding with the correct questions array, and assign that to the questions property:
questionsCD.questions = QuestionsNSSecureCoding(questions: questions)

Casting JSON array to a struct model swift

I am parsing a json array of object into a model which works. In this object, there is an array to a value and I created another model to handle that array but when ever I pass this object, the internal array returns nil after casting as the model class. Any help is appreciated
JSON Sample
[
{
"code": "AF",
"code3": "AFG",
"dial_code": "+93",
"name": "Afghanistan",
"capital": "Kabul",
"region": "Asia",
"subregion": "Southern Asia",
"states": [
{
"code": "BDS",
"name": "Badakhshān",
"subdivision": null
},
{
"code": "BGL",
"name": "Baghlān",
"subdivision": null
}
]
}
}
]
MODEL
public struct LocaleInfo {
public var locale: Locale?
public var id: String? {
return locale?.identifier
}
public var country: String
public var code: String
// public var phoneCode: String
public var states: [LocalStateInfo]
public var flag: UIImage? {
return UIImage(named: "Countries.bundle/Images/\(code.uppercased())", in: Bundle.main, compatibleWith: nil)
}
public var currencyCode: String? {
return locale?.currencyCode
}
public var currencySymbol: String? {
return locale?.currencySymbol
}
public var currencyName: String? {
guard let currencyCode = currencyCode else { return nil }
return locale?.localizedString(forCurrencyCode: currencyCode)
}
init(country: String, code: String/*, phoneCode: String*/, states: [LocalStateInfo]) {
self.country = country
self.code = code
self.states = states
self.locale = Locale.availableIdentifiers.map { Locale(identifier: $0) }.first(where: { $0.regionCode == code })
}
}
public struct LocalStateInfo {
public var code: String
public var name: String
public var subdivision: String
}
Passing the JSON Body
func getInfo(completionHandler: #escaping (FetchResults) -> ()) {
let bundle = Bundle(for: LocalePickerViewController.self)
let path = "Countries.bundle/Data/CountryCodes"
guard let jsonPath = bundle.path(forResource: path, ofType: "json"),
let jsonData = try? Data(contentsOf: URL(fileURLWithPath: jsonPath)) else {
let error: (title: String?, message: String?) = (title: "ContryCodes Error", message: "No ContryCodes Bundle Access")
return completionHandler(FetchResults.error(error: error))
}
if let jsonObjects = (try? JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.allowFragments)) as? Array<Any> {
var result: [LocaleInfo] = []
for jsonObject in jsonObjects {
guard let countryObj = jsonObject as? Dictionary<String, Any> else { continue }
guard let country = countryObj["name"] as? String,
let code = countryObj["code"] as? String/*,
let phoneCode = countryObj["dial_code"] as? String*/ else {
fatalError("Broken here")
continue
}
log("countryObj state \(countryObj["states"] as? [LocalStateInfo])", .fuck)
log("countryObj \(countryObj)", .fuck)
let states = countryObj["states"] as? [LocalStateInfo] ?? [LocalStateInfo]()
let new = LocaleInfo(country: country, code: code/*, phoneCode: phoneCode*/, states: states)
result.append(new)
}
return completionHandler(FetchResults.success(response: result))
}
let error: (title: String?, message: String?) = (title: "JSON Error", message: "Couldn't parse json to Info")
return completionHandler(FetchResults.error(error: error))
}
let states = countryObj["states"] as? [LocalStateInfo] ?? [LocalStateInfo]()
is presumably the line that isn't working for you. But countryObj is just a dictionary straight from JSON:
guard let countryObj = jsonObject as? Dictionary<String, Any> else { continue }
Why would casting it to an array of LocalStateInfo work at at all? It's an array of dictionaries, and you need to parse each one out individually.
You've said using Codable would alter the "entire scope" of the library, I don't understand how this is the case. You can implement codable (or even just Decodable) without affecting any other file.

Typecasting causing struct values to change (Swift)

After downcasting an array of structs, my Variables View window shows that all of the values in my struct have shifted "down" (will explain in a second). But when I print(structName), the values are fine. However, when I run an equality check on the struct, it once again behaves as though my values have shifted.
For example, I am trying to downcast Model A to ModelProtocol. var m = Model A and has the values {id: "1234", name: "Cal"}. When I downcast, m now has the values { id:"\0\0", name:"1234" }.
Actual Example Below:
Models that I want to downcast:
struct PrivateSchoolModel: Decodable, SchoolProtocol {
var id: String
var name: String
var city: String
var state: String
}
struct PublicSchoolModel: Decodable, SchoolProtocol {
var id: String
var name: String
var city: String
var state: String
var latitude: String
var longitude: String
}
Protocol I want to downcast to:
protocol SchoolProtocol {
var id: String { get set }
var name: String { get set }
var city: String { get set }
var state: String { get set }
var longitude: Float { get set }
var latitude: Float { get set }
}
extension SchoolProtocol {
var longitude: Float {
get { return -1.0 }
set {}
}
var latitude: Float {
get { return -1.0 }
set {}
}
}
Downcasting:
guard let downcastedArr = privateSchoolArray as? [SchoolProtocol] else { return [] }
Result (item at index 0) or originalArr:
id = "1234"
name = "Leo High School"
city = "Bellview"
state = "WA"
Result (item at index 0) of downcastedArr:
id = "\0\0"
name = "1234"
city = "Leo High School"
state = "Bellview"
But if I print(downcastArr[0]), it will show:
id = "1234"
name = "Leo High School"
city = "Bellview"
state = "WA"
But if I try originalArray[0].id == downcastArr[0].id, it returns false
My Code with the problem:
class SchoolJSONHandler {
private enum JSONFile: String {
case publicSchool = "publicSchool"
case privateSchool = "privateSchool"
}
private lazy var privateSchoolArray = getPrivateSchools()
private lazy var publicSchoolArray = getPublicSchools()
func getSchoolArray(sorted: Bool, filtered: Bool, by stateAbbreviation: String?) -> [SchoolProtocol] {
var schools = combineArrays()
if sorted {
schools.sort(by: { $0.name < $1.name })
}
if filtered {
guard let abbr = stateAbbreviation else { return [] }
schools = schools.filter {
return $0.state == abbr
}
}
return schools
}
private func combineArrays() -> [SchoolProtocol] {
// EVERYTHING IS FINE IN NEXT LINE
let a = privateSchoolArray
// PROBLEM OCCURS IN NEXT 2 LINES WHEN DOWNCASTING
let b = privateSchoolArray as [SchoolProtocol]
let c = publicSchoolArray as [SchoolProtocol]
return b + c
}
private func getPublicSchools() -> [PublicSchoolModel] {
guard let jsonData = getJSONData(from: .publicSchool) else { return [] }
guard let schools = decode(jsonData: jsonData, using: [PublicSchoolModel].self) else { return [] }
return schools
}
private func getPrivateSchools() -> [PrivateSchoolModel] {
guard let jsonData = getJSONData(from: .privateSchool) else { return [] }
guard let schools = decode(jsonData: jsonData, using: [PrivateSchoolModel].self) else { return [] }
return schools
}
private func getJSONData(from resource: JSONFile) -> Data? {
let url = Bundle.main.url(forResource: resource.rawValue, withExtension: "json")!
do {
let jsonData = try Data(contentsOf: url)
return jsonData
}
catch {
print(error)
}
return nil
}
private func decode<M: Decodable>(jsonData: Data, using modelType: M.Type) -> M? {
do {
//here dataResponse received from a network request
let decoder = JSONDecoder()
let model = try decoder.decode(modelType, from:
jsonData) //Decode JSON Response Data
return model
} catch let parsingError {
print("Error", parsingError)
}
return nil
}
}
And then it is just called in another class by schoolJSONHandler.getSchoolArray(sorted: true, filtered: true, by: "WA")

Json4swift model class

I am new to swift , using json4swift tool to make model class. I would like to know how to get data from model class , i managed to map the items to model class using below code.
let responseModel = Json4Swift_Base(dictionary: searchResultsData)
My json responce folllows:
{
"success": true,
"categorys": [
{
"categoryId": 1,
"categoryName": "Electricians "
},
{
"categoryId": 2,
"categoryName": " Drivers "
},
{
"categoryId": 3,
"categoryName": " Plumbers "
},
{
"categoryId": 4,
"categoryName": "Carpenters "
},
{
"categoryId": 5,
"categoryName": "Automobile works "
}
]
}
Json4swift tool made two classes namely Json4Swift_Base and Categorys class. I need to get from model class.
If you want to learn Swift, I would suggest you to forget json4swift.
First, you have to build your owns models: Category and Response
Category:
struct Category {
let id: Int
let name: String
}
Response:
struct Response {
let success: Bool
let categories: [Category]
}
Second, you want to initialize your models with a JSON. We are going to create a protocol for that:
typealias JSONDictionary = [String : Any]
protocol JSONDecodable {
init?(dictionary: JSONDictionary)
}
Your models must implement this protocol, so we add extensions:
Category extension:
extension Category: JSONDecodable {
init?(dictionary: JSONDictionary) {
guard let id = dictionary["categoryId"] as? Int,
let name = dictionary["categoryName"] as? String else {
return nil
}
self.id = id
self.name = name
}
}
Response extension:
extension Response: JSONDecodable {
init?(dictionary: JSONDictionary) {
guard let success = dictionary["success"] as? Bool,
let jsonCategoriesArray = dictionary["categorys"] as? [JSONDictionary] else {
return nil
}
self.success = success
self.categories =
jsonCategoriesArray.flatMap{ jsonCategoryDictionary in
Category(dictionary: jsonCategoryDictionary)
}
}
}
Now, you can write:
let response = Response(dictionary: jsonResponse)
if let response = response {
let success = response.success
let categories = response.categories
let firstCategory = categories[0]
// ...
}

How to create objects from SwiftyJSON

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
}
}
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.
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
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
for (item, content) in hoge {
let level = content["level"].intValue
}
that should work