KTV. Новий JSON

У своєму розвитку мені довелося пробігтися по декільком етапам в декількох напрямках: Java → Objective C → Swift, Web → Enterprise → Mobile, XML → JSON. Цим шляхом я йшов протягом більше 15 років, довго і уважно затримуючись на кожному етапі. Треба йти далі. За мобільними додатками можна придумати що-то (мабуть, поки не хочеться), мов взагалі ставок-гати, нічого цікавіше JSON'а не придумали. А навіщо його міняти?

Далі я розповім чому мені не дуже подобається JSON для деяких застосувань і як, на мою думку, його можна доопрацювати, щоб стало трохи зручніше.

Відразу мушу зазначити, що не розглядаю KTV, як заміну JSON. І ні в якому разі не розглядаю його для використання в JavaScript'тобто Це буде незручно і неправильно. З іншого боку, ситуація, коли система опису об'єктів JavaScript'а використовується в інших мовах для роботи з типізованими даними — теж дивна, і її хочеться поправити.

Якщо вам цікаво, що стало початковою причиною створення такого формату — можна почитати про S2 і причини створення формату KTV

Плюси і мінуси JSON
Сам по собі JSON хороший.

  • Простий. Окреме спасибі за відсутність коментарів. Якщо б їх залишили, туди почали б пхати мета-інформацію (тому що JSON дуже бідний) і нотація швидко перетворилася б на смітник.
  • Виразний. Не так, як XML, але достатньо для широкого кола завдань.
  • Сильно коротше в записі, ніж XML.
Хвороби JSON'а ростуть з історії створення. Потрібно було, щоб
eval ()
в JavaScript'е видавав об'єкт, з яким зручно працювати. На жаль, обчислені и в інших мовах працюють зазвичай по-іншому (якщо взагалі є), і самі мови відрізняються. Використовуючи цей формат не-JavaScript, видно його недоліки.

  • Лапки. За стандартом (в якості стандарту я беру текст звідси: http://www.json.org) всі назви повинні полягати в лапки. Якщо ми використовуємо JSON, наприклад, для передачі даних по мережі, то це надмірність.
  • Відсутність типізації. Точніше, типи є, але все рядок/кількість/true/false/null. І об'єкти з масивами. Всі. Ні дат, ні цілих/дробових чисел немає. Немає можливості позначити типами об'єкти, щоб простіше було модель зрозуміти.
  • Відсутність стандартів запису об'єктів. Це, наприклад, може призвести до масивів зі змішаними об'єктами всередині. Коли припадає таке парсити — стає боляче.
  • Відсутність посилань. Якщо в JSON записувати ієрархічну структуру об'єктів, то регулярно зустрічаються посилання на один і той же об'єкт з різних місць. JSON не дає можливості послатися на перше використання. Потрібно або щось придумувати, або повторювати об'єкти цілком, що погано позначається на розмірі структури і складності парсинга.
Я спробував знайти формат, який би працював як JSON (в ідеалі — був би його надмножеством), але при цьому вмів стандартним чином вирішувати частину або всі перераховані вище проблеми. Знайшов YAML.

YAML
YAML хороший. Він майже надмножество JSON, добре визначений (http://www.yaml.org/spec/1.2/spec.html), легко читається. Але разом з тим:

  • вкладеність структур визначається відступами. У випадку з передачею інформації зручна була б можливість їх прибирати. Любителів табуляції також не поважають, відступи робляться лише пробілами.
  • YAML надмірно складний. Велика кількість спеціальних символів, домовленості про структуру роблять підтримку YAML'а складною, парсери — складними (YAML 1.2 з'явився вже 7 років, але, згідно новин з сайту, парсери досі підтримують в основному попередні версії).
Загалом, YAML «не пішов». Крім нього популярних форматів я не знаю. Якщо раптом ви знаєте — пишіть, із задоволенням подивлюся.

Прийшов час представити те, що вийшло у мене.

KTV
Key-Type-Value. Я так називаю цей формат.

Напишемо простий об'єкт на JSON'е:

{
"name": "Alex",
"coolness": 3.1415,
"isAProgrammer": true
} 

Якщо переписати це ж на KTV, вийде:

{
name: Alex
coolness: 3.1415
isAProgrammer: true
}

Здавалося б, нічого не змінилося. Але давайте придивимося:

  • забралися лапки. Вони тепер обов'язкові тільки, якщо в ключі або значенні є нетривіальна рядок. Звичайні ситуації лапок не вимагають.
  • забралися коми в кінці рядків. Їх (або крапки з комою) можна ставити, якщо пишемо кілька визначень в один рядок. Але в традиційній запису їх можна опустити.
І перший і другий приклади — обидва є коректними KTV-файлами. Це дуже важливо, так як дозволяє KTV-парсер запхати стандартний JSON, і він його з'їсть із задоволенням. А можна скористатися зручностями, типами наприклад.

Де ж тут типи? У вищенаведеному випадку типи виводяться з значень. «Alex» — це рядковий літерал, значить тип name'а — «string», coolness'у дістався «double», а isAProgrammer — «bool». Крім цих типів є ще «null» (він же «nil», привіт колегам), «int» і «color». Давайте запишемо цей самий приклад, але повністю вказуючи типи:

{
name(string): Alex
coolness(double): 3.1415
isAProgrammer(bool): true
}

Нікуди не поділися і вкладені об'єкти/масиви. З доповненнями:

  • І об'єктів та у масивів можуть бути типи. Для масиву тип — це загальний тип кожного об'єкта в масиві. Не можна покласти два різних типу (рядок/число, або різні об'єкти) в масив.
  • Тип масиву або об'єкта також може вказувати на клас, до якого цей об'єкт або масив необхідно привести при парсингу. Наприклад:
    property(font): {name:..., size:...}

    може позначати об'єкт-шрифт
    property(point): [2, 3]

    може при розпізнаванні бути перетворено в об'єкт-точку. В принципі, точку можна зробити також об'єктом (з пропертями x, y), але навіщо? Адже ми і так знаємо, що там де.
Присутні посиланням, можливість послатися на будь-яку іншу пропертю. Якось так:

{
property: value
another: @property
}

Це дозволяє скоротити запис деяких гіллястих об'єктів, або просто ставити константи, звертаючись до них в інших частинах файлу. Перша можливість корисна в REST'е, якщо структура об'єктів нетривіальна. Друга — в конфігураційних файлах.

Ідея миксинов також запозичена з інших мов. Вони дозволяють проперті одного об'єкта (чи об'єктів) приробити іншому об'єкту. Можна уявити собі це, як наслідування, а наслідування — це з ООП, там поведінка успадковується, тут тільки властивості. Так що краще змішувати.

{
object: { a: ..., b: ...}
object2(+object): { c: ..., d: ... }
}

В даному прикладі об'єкт object2 буде після парсинга містити як свої проперті, так і проперті з об'єкту object. Тобто, a, b, c і d.

Коментарі
Я не втримався і організував коментарі. Їх можна задавати або за допомогою / / або #. Обидва типи коментарів — рядкові, блокових коментарів немає.

З другим типом (який октоторповый) є невелика проблема. Справа в тому, що літерал кольору передбачається ставити як-раз в стандартному CSS-вигляді, тобто #rrggbbaa, що конфліктує з поточним варіантом запису коментарів. Так що, можливо, в майбутньому залишиться тільки З-варіант (//).
Робота з форматом
Для мене цей формат, і парсер цього формату потрібен в двох місцях:

  • щоб зберігати стильову інформацію у S2 (розвиток Ångström Style System
  • щоб можна було покращити спілкування між своїми власними серверами на Swift і додатками на ньому.
Тому я розглядаю роботу з форматом саме для Swift'а. В принципі, формат досить простий і не повинно бути складно організувати його парсер в будь-якій мові.

Проблеми Swift зараз не дозволяють зробити по-справжньому правильну завантаження/серіалізацію об'єктів з/в KTV. Тому процес роботи з форматом передбачається наступний:

  • Створюються модельні класи. Як звичайні класи (в тому числі анотовані
    @objc
    ) або структури. Кожен клас повинен реалізовувати протокол
    KTVGenerated
    .
  • За допомогою генератора створюються розширення до цих класів, які вміють завантажувати і сериализовывать об'єкти.
  • Використовуємо згенероване для перетворення KTV в об'єкти і назад (або в/з JSON).
Модельна структура може виглядати, наприклад, так:

public struct RootObject: KTVGenerated {
var string): String // просто пропертя
var stringOrNil:String? // optional
var int:Int // числа

var stringArray:[String] // масиви
var stringDictionary:[String:Int] // асоціативні масиви

var object:ChildObject? // посилання на інший модельний клас

private var _privateString:String // ця пропертя не буде сериализовываться
private(set) var _privateSetString:String // ця — теж
let _constantString:String // та константи теж не сериализуем
}

Видно, що підтримуються всі основні фічі:

  • Типи (вони повинні прописуватися явно, щоб генератор зміг коректно створити розширення класів.
  • Колекції (масиви і асоціативні масиви).
  • Ієрархії об'єктів (посилання на інші модельні класи)
  • Проперті, які не беруть участь у серіалізації (це константи, приватні проперті і проперті з приватними сеттерами)
Історія створення такого парсера — тема для окремої статті. Завдання одночасного підтримання структур і класів, способи задання атрибутів без наявності самих атрибутів, використання SourceKit'а замість рефлекшна для аналізу файлів, необхідність кастомних мапперов (наприклад, для парсингу нестандартних дат). Сам парсер зараз в процесі активної розробки, і постійно змінюється. Але, якщо буде достатній інтерес — викладу робочий проект, обговоримо варіанти.

Користь від чергового формату даних
image
https://xkcd.com/927/
JSON — так собі формат для передачі даних між додатками. Це — формат опису об'єктів в JavaScript'е. А ми постійно використовуємо його для зовсім інших цілей, впихаючи і по справі і без діла.

Мені здається, пора переходити до чогось, більш пристосованому до виконуваних завдань. До нового Swift'у з його строгою типізацією, до складних структур даних, які часто доводиться передавати на мобільні клієнти з сервера. Потрібний формат, синтаксис якого можна буде перевіряти в IDE, в процесі компіляції і при парсингу. І, мені здається, що KTV, не змінюючи кардинально структуру, практично не ускладнюючи вже відомий формат, додає кілька зручних дрібниць.

Викладаю експериментальний вихідний код з невеликим описом. Там більше цікаві прийоми для парсингу формату на Swift, ніж парсер. Але раптом кому цікаво буде подивитися: http://github.com/bealex/KTV

Швидше за все, я багато чого упустив або не врахував. Може, у когось є подібний досвід? Пишіть в коментарях, або на пошту: alex@jdnevnik.com, обговоримо!

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.