KTV. Новый JSON

в 9:01, , рубрики: json, ktv, парсер json, парсинг json, парсинг объектов, разработка мобильных приложений, разработка под iOS, форматы файлов

В своём развитии мне пришлось пробежаться по нескольким этапам в нескольких направлениях: Java → Objective C → Swift, Web → Enterprise → Mobile, XML → JSON. Этим путём я шёл на протяжении более 15 лет, подолгу и внимательно задерживаясь на каждом этапе. Нужно идти дальше. За мобильными приложениями можно придумать что-то (наверное, пока не хочется), языков вообще пруд-пруди, ничего интереснее JSON'а не придумали. А зачем его менять?

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

Сразу должен отметить, что не рассматриваю KTV, как замену JSON. И ни в коем случае не рассматриваю его для использования в JavaScript'е. Это будет неудобно и неправильно. С другой стороны, ситуация, когда система описания объектов JavaScript'а используется в других языках для работы с типизированными данными — тоже странная, и её хочется поправить.

Плюсы и минусы JSON

Сам по себе JSON хороший.

  • Простой. Отдельное спасибо за отсутствие комментариев. Если бы их оставили, туда начали бы пихать мета-информацию (потому что JSON бедный очень) и нотация быстро превратилась бы в помойку.
  • Выразительный. Не так, как XML, но достаточно для широкого круга задач.
  • Сильно короче в записи, чем XML.

Болезни JSON'а растут из истории создания. Нужно было, чтобы eval(что-то) в JavaScript'е выдавал объект, с которым удобно работать. К сожалению, eval'ы в других языках работают обычно по-другому (если вообще присутствуют), и сами языки отличаются. Используя этот формат с не-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, не меняя кардинально структуру, практически не усложняя уже известный формат, добавляет несколько удобных мелочей.

Скорее всего, я много чего упустил или не учёл. Может, у кого-то есть похожий опыт? Пишите в комментариях, или в почту: alex@jdnevnik.com, обсудим!

Автор: bealex

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js