Работа с JSON — слишком привычное и ежедневное занятие, чтобы уделять ей много внимания. Тем не менее, реализация некоторых вещей в Swift выглядит слишком сложной и вызывает зубовную боль каждый раз, когда ее видишь.
Недавно, читая пост про SwiftyVK, нашел там ссылку на статью про OptJSON, позволяющую сильно упростить работу с JSON в Swift. И хотя подход, описанный в статье, действительно интересен, меня не покидало ощущение, что это все-равно слишком сложно.
Я попробовал еще немного упростить библиотеку OptJSON, и вот что получилось:
let obj = json?["workplan"]?["presets"]?[1]?["id"] as? Int
Исходный код библиотеки можно посмотреть по ссылке на GitHub:
Скачав единственный файл OptJSON.swift, я добавил его в пустой, только что созданный тестовый проект под Apple TV. Xcode ругнулся, что версия Swift в файле слишком старая и предложил обновить код. Я не стал возражать. По факту исправления коснулись лишь удаления хэш-символов (#).
Также я включил в проект JSON-конфиг, который использовал ранее в другом проекте и попробовал написать тестовый код для извлечения какого-нибудь значения «по-старинке»:
if let data = NSData(contentsOfFile: NSBundle.mainBundle().pathForResource("config", ofType: "json")!) { do { let obj = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? [String: AnyObject] let a = obj?["workplan"] as? [String: AnyObject] let b = a?["presets"] as? [AnyObject] print(b) let c = b?[1] as? [String: AnyObject] print(c) let d = c?["id"] as? Int print(d) } catch { print("Error!") } }
Да, в Objective C действительно было в разы проще. Теперь попробуем использовать OptJSON для тех же целей:
if let data = NSData(contentsOfFile: NSBundle.mainBundle().pathForResource("config", ofType: "json")!) { do { let obj = try NSJSONSerialization.JSONObjectWithData(data, options: []) let v = JSON(obj) let a = v?[key:"workplan"] let b = a?[key:"presets"] print(b) let c = b?[index:1] print(c) let d = c?[key:"id"] print(d) } catch { print("Error!") } }
Или если коротко:
if let data = NSData(contentsOfFile: NSBundle.mainBundle().pathForResource("config", ofType: "json")!) { do { let obj = try NSJSONSerialization.JSONObjectWithData(data, options: []) let d = JSON(obj)?[key:"workplan"]?[key:"presets"]?[index:1]?[key:"id"] print(d) } catch { print("Error!") } }
Уже неплохо! Но все равно, не покидает ощущение, что можно все сделать проще. И ведь можно! Я полез в OptJSON.swift и, первым делом, меня удивила конструкция:
public func JSON(object: AnyObject?) -> JSONValue? { if let some: AnyObject = object { switch some { case let null as NSNull: return null case let number as NSNumber: return number case let string as NSString: return string case let array as NSArray: return array case let dict as NSDictionary: return dict default: return nil } } else { return nil } }
Не долго думая, я ее заменил на
public func JSONValue(object: AnyObject?) -> JSONValue? { if let some = object as? JSONValue { return some } else { return nil } }
А если нет разницы, зачем платить больше? Следующим шагом было убрать именованные параметры, так сильно бесящие при вызове subscript:
[key:"presets"]?[index:0]?
Сказано — сделано! Xcode ругнулся, что ему не нравится возвращаемый тип JSONValue? вместо ожидаемого subscript-ом AnyObject?.. Чтож, попутно сносим обертку возвращаемых значений в JSON(). Код обрел примерно следующий вид:
extension NSArray : JSONValue { public subscript( key: String) -> JSONValue? { return nil } public subscript( index: Int) -> JSONValue? { return index < count && index >= 0 ? self[index] : nil } } extension NSDictionary : JSONValue { public subscript( key: String) -> JSONValue? { return self[key] } public subscript( index: Int) -> JSONValue? { return nil } }
Запустив проект, я понял, почему автор решил использовать именованные параметры, а именно, выполнение функции уходило в глубокую рекурсию, пытаясь вызвать self[key]. Но в конце концов, почему для расширения используются классы NSArray и NSDictionary, а для извлечения объекта — чуждый objectForKeyedSubscript?! Ведь есть же родные для этих классов методы objectForKey: и objectAtIndex:, используем их:
extension NSArray : JSONValue { public subscript( key: String) -> JSONValue? { return nil } public subscript( index: Int) -> JSONValue? { return index < count && index >= 0 ? self.objectAtIndex(index) as? JSONValue : nil } } extension NSDictionary : JSONValue { public subscript( key: String) -> JSONValue? { return self.objectForKey(key) as? JSONValue } public subscript( index: Int) -> JSONValue? { return nil } }
А раз пошла такая пьянка, то функция JSON() для обертки объектов нам в принципе не нужна, хватит и простого as? JSONValue. Заменим ее внутренности для чего-нибудь более удобного, например загрузка JSON объекта из строки, NSData или из содержимого NSURL, а заодно избавимся от необходимости использования новомодного try-catch:
public func JSON(object: AnyObject?, options: NSJSONReadingOptions = []) -> JSONValue? { let data: NSData if let aData = object as? NSData { data = aData } else if let string = object as? String, aData = string.dataUsingEncoding(NSUTF8StringEncoding) { data = aData } else if let url = object as? NSURL, aData = NSData(contentsOfURL: url) { data = aData } else { return nil } if let json = try? NSJSONSerialization.JSONObjectWithData(data, options: options) { return json as? JSONValue } return nil }
После этих преобразований, конечный код приобретает следующий вид:
if let v = JSON(NSBundle.mainBundle().URLForResource("config", withExtension: "json")) { let a = v["workplan"] let b = a?["presets"] print(b) let c = b?[1] print(c) let d = c?["id"] print(d) }
А если короче, то и вовсе:
let json = JSON(NSBundle.mainBundle().URLForResource("config", withExtension: "json")) let obj = json?["workplan"]?["presets"]?[1]?["id"] as? Int print(obj)
И никаких именованных параметров! Спасибо за внимание.
import Foundation public protocol JSONValue: AnyObject { subscript(key: String) -> JSONValue? { get } subscript(index: Int) -> JSONValue? { get } } extension NSNull : JSONValue { public subscript(key: String) -> JSONValue? { return nil } public subscript(index: Int) -> JSONValue? { return nil } } extension NSNumber : JSONValue { public subscript(key: String) -> JSONValue? { return nil } public subscript(index: Int) -> JSONValue? { return nil } } extension NSString : JSONValue { public subscript( key: String) -> JSONValue? { return nil } public subscript( index: Int) -> JSONValue? { return nil } } extension NSArray : JSONValue { public subscript( key: String) -> JSONValue? { return nil } public subscript( index: Int) -> JSONValue? { return index < count && index >= 0 ? self.objectAtIndex(index) as? JSONValue : nil } } extension NSDictionary : JSONValue { public subscript( key: String) -> JSONValue? { return self.objectForKey(key) as? JSONValue } public subscript( index: Int) -> JSONValue? { return nil } } public func JSON(object: AnyObject?, options: NSJSONReadingOptions = []) -> JSONValue? { let data: NSData if let aData = object as? NSData { data = aData } else if let string = object as? String, aData = string.dataUsingEncoding(NSUTF8StringEncoding) { data = aData } else if let url = object as? NSURL, aData = NSData(contentsOfURL: url) { data = aData } else { return nil } if let json = try? NSJSONSerialization.JSONObjectWithData(data, options: options) { return json as? JSONValue } return nil }
ссылка на оригинал статьи http://habrahabr.ru/post/270063/
Добавить комментарий