In this post we continue the development of a JSON Parser starting from the previous one: Swift JSON Parser – Part 1.
Let us begin handling the “something went wrong” part.
Error handling strategy
Two types of “bad things” can happen:
- The parser has an error before looping on the elements of the array: this can be a reader error or the json returned is not of type [AnyObject?]
- The parser starts but has an error on one of the element inside the loop
For errors of type 1 we add a second callback parameter to start.
Add a return value to the handleData method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
func handleData(data : NSData, parserNewPhoto : ParserNewPhoto) -> NSError? { var error : NSError? let json : AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions(0), error: &error) if let _json = json as? [AnyObject] { for jsonItem in _json { // code to loop on the array... } } else { error = NSError(domain: "Parser", code: 100, userInfo: [NSLocalizedDescriptionKey:"Json is not an array of objects"]) } return error } |
and modify start:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
func start(reader : ParserReader, errorCallBack : (NSError) -> (), parserNewPhoto : ParserNewPhoto) { var error : NSError? reader() { (result : ReaderResult)->() in switch result { case let .Error(readError): error = readError case let .Value(fileData): error = self.handleData(fileData, parserNewPhoto) if let _error = error { errorCallBack(error!) } } } |
The method calling the parser will handle a possible error using the second parameter of start (a callback):
1 2 3 4 5 6 |
let parserTestReader = readJsonFile("test") let parser = Parser() parser.start(parserTestReader, { (error : NSError) in println(error) } ) { (photoResult : PhotoResult) -> Bool in // code handling a single photo } |
Now we have to handle the errors of type 2, the ones arising inside the loop. For this we modify the callback ParserNewPhoto from:
1 |
typealias ParserNewPhoto = (Photo)->() |
to:
1 |
typealias ParserNewPhoto = (PhotoResult)->Bool |
What is PhotoResult?
We are use the same technic we used for the reader: an enum with associated value:
1 2 3 4 |
enum PhotoResult { case Value(Photo) case Error(NSError) } |
The enum evals to different values depending on the result of the operation.
If a photo was created successfully we invoke the callback with:
1 |
parserNewPhoto(PhotoResult.Value(photo)) |
otherwise:
1 |
parserNewPhoto(PhotoResult.Error(photoError)) |
Why not a “generic”?
We defined twovery similar enums:
1 2 3 4 5 6 7 8 9 |
enum ReaderResult { case Value(NSData) case Error(NSError) } enum PhotoResult { case Value(Photo) case Error(NSError) } |
This situation would be perfect for a swift generic:
1 2 3 4 |
enum Result<A> { case Value(A) case Error(NSError) } |
And THIS IS THE RIGHT IMPLEMENTATION. But at the moment (and since some time) there is a bug in the swift compiler that prevent this:
There are some workarounds, like boxing the value inside a class and make that class the associated value, like in:
1 2 3 4 5 6 7 8 9 10 |
class MyBox<A> { let v: A init(v: A) { self.v = v } } enum Result<A> { case Error(NSError) case Value(MyBox<A>) } |
but I prefer not to confuse the (already complex) situation. Remember to fix this when Apple fix its bug!
Continue or stop?
The parser wants to know what to do after an error. Should it continue or stop execution? This is why we added the Bool return value to the ParserNewPhoto callback: we ask it to the callback. By the way I don’t like very much this name “ParserNewPhoto”, when we will make this parser generic (not about photos) we will change that name.
This is the code inside loop with error handling:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
... for jsonItem in _json { if let _jsonItem = jsonItem as? [String: AnyObject] { let titolo : AnyObject? = _jsonItem["titolo"] let autore : AnyObject? = _jsonItem["autore"] let latitudine : AnyObject? = _jsonItem["latitudine"] let longitudine : AnyObject? = _jsonItem["longitudine"] let data : AnyObject? = _jsonItem["data"] let descr : AnyObject? = _jsonItem["descr"] var ok = false var toStop = false if let _titolo = titolo as String? { if let _autore = autore as? String { if let _latitudine = latitudine as? Double { if let _longitudine = longitudine as? Double { if let _data = data as? String { if let _descr = descr as? String { let photo = Photo(titolo: _titolo, autore: _autore, latitudine: _latitudine, longitudine: _longitudine, data: _data, descr: _descr) toStop = parserNewPhoto(PhotoResult.Value(photo)) if toStop { break } ok = true } } } } } } if (!ok) { // don't override error let photoError = NSError(domain: "Parser", code: 101, userInfo: [NSLocalizedDescriptionKey:"Errore su un elemento dell'array"]) toStop = parserNewPhoto(PhotoResult.Error(photoError)) if toStop { break } } } } ... |
And the parser caller (in viewDidLoad of our project):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
let parserTestReader = readJsonFile("test") let parser = Parser() parser.start(parserTestReader, { (error : NSError) in println(error) } ) { (photoResult : PhotoResult) -> Bool in switch photoResult { case let .Error(photoError): println("Errore: " + photoError.localizedDescription) case let .Value(photo): println(photo.data + ": " + photo.titolo) } return false // continue always } |
Ok for now this is enough for error handling.
More elegant optional binding
Now focus on the handle data body and, in particular all that optional checks inside the loop:
We have many lines as:
1 2 3 4 |
let titolo : AnyObject? = _jsonItem["titolo"] ... if let _titolo = titolo as String? { ... |
Let’s define a function:
1 2 3 |
func StringFromJSON(ao : AnyObject?) -> String? { return ao as? String } |
function explained: if ao (any object) is not nil and can be typecasted to String return the String, otherwise return null.
Then we can write the previous lines simply as:
1 2 |
if let _titolo = StringFromJSON(_jsonItem["titolo"]) { ... |
Define it also for Doubles:
1 2 3 |
func DoubleFromJSON(ao : AnyObject?) -> Double? { return ao as? Double } |
and then our optionals check becomes:
1 2 3 4 5 6 7 8 |
... if let _titolo = StringFromJSON(_jsonItem["titolo"]) { if let _autore = StringFromJSON(_jsonItem["autore"]) { if let _latitudine = DoubleFromJSON(_jsonItem["latitudine"]) { if let _longitudine = DoubleFromJSON(_jsonItem["longitudine"]) { if let _data = StringFromJSON(_jsonItem["data"]) { if let _descr = StringFromJSON(_jsonItem["descr"]) { ... |
Little better, but the story is not finished. In the next posts we are going to play with “Refactoring” and “Custom operators”.
Here is code of Part 2: Gihub project