Swift код к статье Tony DiPasquale “Реальный мир парсинга JSON в Swift”

В этом посте представлен Swift код, который сопровождал изучение и перевод статьи Tony DiPasquale  “Реальный мир парсинга JSON в Swift” (перевод) – оригинал Real World JSON Parsing with Swift .

Автор предлагает Swift код в самих статьях, но это, как правило, только результирующий вариант и с очень малым количеством примеров.

Эксперименты с алгоритмами Тони ДеПаскуале проводились в Swift 1.1, и представлены в репозитории на Github, который включает в себя различные  Playground файлы и полноценное небольшое приложение для работы  с данными с сайта Flickr.com.

Тестирование алгоритмов, представленных в данной статье, будем выполнять на примерам, которые предлагает автор, но немного расширенные за счет иммитации различных ошибок, которые мы должны “поймать” на выходе.

Результаты изучения статьи  “Реальный мир парсинга JSON в Swift” представлены в единственном файле  EfficientJSONRealLife.playground. Это тестирование проходит строго в соответствии с тем, как выдвигаются идеи в статье.

В этой статье рассматривается очень простая Модель User, в которой email может быть в JSON-данных, а может и не быть, поэтому он представлен Optional:

struct User: JSONDecodable, Printable {
    let id: Int
    let name: String
    let email: String?

Для того, чтобы иметь дело с Optional в Модели вводится функция pure

//-------pure функцию ---

func pure<A>(a: A) -> A? {
    return .Some(a)
}

и используется такая возможнсть Swift, как type inference , что означает, что тип данных будет выводиться из контекста. Такая возможность есть, так как в структуре Модели указан тип данных каждой компоненты.
Будем использовать универсальный (generic) парсер для отдельных элементов структуры:

func _JSONParse<A>(json: JSON) -> A? {
    return json as? A

Это позволит записать Модель User следующим образом:

struct User: JSONDecodable, Printable {
    let id: Int
    let name: String
    let email: String?

  static func create(id: Int)(name: String)(email: String?) -> User {
    return User(id: id, name: name, email: email)
  }

  static func decode(json: JSON) -> User? {
    return _JSONParse(json) >>> { (d: JSONObject) in
      User.create <^>
             d["id"]          >>> _JSONParse  <*>
             d["name"]        >>> _JSONParse  <*>
        pure(d["email"]       >>> _JSONParse)
    }
  }
}

Для сокращения парсинга каждого элемента вводим функции извлечения данных из словаря:

func extract<A>(json: JSONDictionary, key: String) -> A? {
    return json[key] >>> _JSONParse
}

func extractPure<A>(json: JSONDictionary, key: String) -> A?? {
    return pure(json[key] >>> _JSONParse)
}

тогда функция decode Модели User приобретет такой вид:

//~~~~~~~~~~~~~~~~ МОДЕЛЬ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
struct User: JSONDecodable, Printable {
    let id: Int
    let name: String
    let email: String?

    var description : String {
        return "User { id = \(id), name = \(name), email = \(email)}"
    }

    static func create(id: Int)(name: String)(email: String?) -> User {
        return User(id: id, name: name, email: email)
    }

//---------- ВАРИАНТ с extract -------------

    static func decode(json: JSON) -> User? {

           return _JSONParse(json) >>> { d in
                User.create
                    <^> extract (d,"id")
                    <*> extract (d,"name")
                    <*> extractPure (d,"email")
            }
    }
}

Продолжаем делать нашу Модель User более компактной. Для этого введем операторы извлечения данных из JSON:

//-------- Операторы извлечения данных из JSON-------

infix operator <| { associativity left precedence 150 }
infix operator <|* { associativity left precedence 150 }

//---------
func <|<A>(json: JSONDictionary, key: String) -> A? {
    return json[key] >>> _JSONParse
}

func <|*<A>(json: JSONDictionary, key: String) -> A?? {
    return pure(json[key] >>> _JSONParse)
}

И получаем окончательный вид Модели User:

//~~~~~~~~~~~~~~~~ МОДЕЛЬ User ~~~~~~~~~~~~~~~~~~~~~~~~~~~
struct User: JSONDecodable, Printable {
    let id: Int
    let name: String
    let email: String?

    var description : String {
        return "User { id = \(id), name = \(name), email = \(email)}"
    }

    static func create(id: Int)(name: String)(email: String?) -> User {
        return User(id: id, name: name, email: email)
    }

//---------- ОКОНЧАТЕЛЬНЫЙ ВАРИАНТ -------------

    static func decode(json: JSON) -> User? {
        return _JSONParse(json) >>> { d in
            User.create
                <^> d <|  "id"
                <*> d <|  "name"
                <*> d <|* "email"
        }
    }
}

Создаем функцию для парсинга и проверяем на разных данных:
Screen Shot 2014-11-04 at 9.06.30 PM

Leave a Reply

Your email address will not be published. Required fields are marked *