Здравствуйте, судя по моему скромному опыту, с таким форматом обменом данными, как JSON, знакомы очень многие. Сейчас он является де‑факто стандартом в веб‑приложениях (при обмене данными между клиентом и сервером), а некоторые производные формы JSON используются и в других местах — например, BSON в MongoDB или MessagePack в Pinterest или Redis (поддерживается в скриптинге). Я тоже хорошо знаком с JSON и довольно длительное время регулярно его использовал, однако...
Однажды мне в голову пришла мысль: «А почему бы не попробовать сократить JSON, написав свой формат и оставив его при этом совместимым со своим родителем?». Сказано — сделано, благо что в тот момент я писал в качестве пет‑проекта маленький движок для текстовых квестов, конфиги и темы к которому писались именно на JSON, то есть пространство для экспериментов было. Так как я уже был знаком с тем, как писать формальные грамматики для ANTLR, я быстро набросал её концепт, взяв за основу грамматику JSON и добавив сахара по вкусу (до зубного скрежета). Так как мой движок уже назывался Lirot (с иврита — «видеть»), я не стал заморачиваться и назвал новый язык для его конфигурационных Av — это пятый месяц еврейского календаря, а ещё он примерно соответствует июлю — началу августа. Это было как раз кстати, ведь Av я начал писать именно в июле.
Что из нового я добавил в Av:
Комментарии — есть только однострочные, начинаются на точку с запятой и идут до конца строки;
Идентификаторы (Id) — можно использовать вместо строк там, где ожидается ключ или значение. Представляют собой просто целые слова;
Шестнадцатеричные целые числа (HexInt) — беззнаковые, иногда удобно писать сразу их, а не строки. Например, я использовал их в конфигах тем для движка;
Байтовые последовательности (bytes), которые можно составлять из HexInt с любым количеством знаков (желательно разумным). Концепт подразумевает, что выравнивание и разбивка последовательностей на целые числа определённой разрядности лежит на плечах реализующего, в моей реализации на Java они парсятся как Byte);
Биндинги на уровне массивов и мап (в Av так называются объекты) — полезная штука для сокращения повторяемых значений;
Ссылки на существующие константы или на их свойства;
Чёткое разделение number на int и float. Int представлен десятичными и шестнадцатеричными литералами;
Отдельный тип bool, к которому принадлежат true и false;
Nil — прямой аналог null, который в некоторых случаях помогает сократить объём текста, а также просто нравится мне больше;
Общая категория atom, куда входят int, float, string, bool, nil и id;
Дополнительные виды ключей в мапах — можно использовать не только строки, но и любые атомы;
Опциональные запятые в конце массивов и мап.
Интерполяция строк;
Опциональные фигурные скобки в топ‑левеле;
Семь видов заимствований (borrow).
Что я убрал (опционально) из JSON:
Кавычки возле коротких строковых ключей и значений (в большинстве случаев можно заменить на id);
Двоеточия в объектах;
Запятые в объектах и массивах.
Да, конечно, я добавил в JSON гораздо больше, чем убрал, однако фактически при замерах Av почти всегда оказывается компактнее JSON. Сначала я расскажу о каждом из изменений, а затем сравню Av с JSON. Примечание: большая часть примеров будет браться с сайта jsoneditoronline.org.
Нововведения
Ниже для каждого нововведения я буду приводить пример текста на JSON, если для него есть прямое соответствие, а под ним — на Av.
1. Комментарии
Av поддерживает однострочные комментарии, которые начинаются на точку с запятой. Спецификация JSON не описывает комментарии, однако некоторые реализации закрывают на это глаза и добавляют однострочные, многострочные или оба вида сразу.
// this is a comment /* this is a comment too */
; this is a comment
2. Идентификаторы
Идентификаторы позволяют использовать достаточно большое количество строк из одного слова без кавычек. Ниже представлена формальная грамматика, хотя есть и несколько исключений, не рассматриваемых как Id.
Id: [a-zA-Z_0-9+/%|&^<=>*!?\u0391-\u03A9\u03B1-\u03C9\-]+;
{ "name":"Chris", "age":23, "city":"New York" }
{ name:Chris, age:23, city:"New York" }
3. Шестнадцатеричные числа
Почти во всех местах, где я видел шестнадцатеричные числа, они писались с префиксами 0x или #. Unreal Engine (color picker в виджетах), Blender, Photoshop, CSS и другие инструменты используют именно второй вариант, да и мне он нравится больше, поэтому я выбрал его.
#CAFEBABE ; #cafebabe, #CaFeBaBe
4. Байтовые последовательности
Записываются в виде круглых скобок, внутри которых есть как минимум одно шестнадцатеричное число. Можно группировать числа как угодно, пробелы при этом не требуются. Допустимо группировать числа с количеством нибблов, не равным степени двойки (правда, потом вам придётся самим решать, что с ними делать, но всё же допустимо).
; space is not neccessary (#89#50#4E#47#0D#0A#1A#0A) ; grouped to bytes (#89 #50 #4E #47 #0D #0A #1A #0A) ; grouped to 2 bytes (#8950 #4E47 #0D0A #1A0A) ; grouped to 4 bytes (#89504E47 #0D0A1A0A) ; grouped to 8 bytes (#89504E470D0A1A0A)
5. Биндинги
Av позволяет создавать константы на уровне мап и массивов. Они предназначены для сокращения дублированного текста, а их значения могут быть в любое время получены с помощью ссылок, о которых я расскажу позднее.
Есть два вида констант: временные (temporary) и постоянные (persistent). Разница в том, что временные константы не затрагивают структуру массива или мапы, а постоянные напрямую встраиваются в них. В случае с мапой создаётся новая пара ключ‑значение, а в случае с массивом в него добавляется значение, которое в дальнейшем тоже будет доступно по ссылке. Синтаксически разница очень простая:
{ foo := 42 ; temporary bar ::= 4 ; persistent, will also declare "bar": 4 }
Область видимости у биндингов (как и у ссылок) лексическая. При определении константы с именем, которое уже есть в данной области видимости, старое определение затеняется, при этом в каждой области видимости есть доступ к константам, которые были объявлены до её создания. Если биндинг a объявлен перед биндингом b, то нельзя написать a := b, поскольку в момент объявления a ресолвер Av ещё не знает про b. Следовательно, создать рекурсивные биндинги нельзя.
{ thumbnails := { ; temporary bind url := "https://i.ytimg.com/vi/" default { url 'url width 120 height 90 } medium { url 'url width 320 height 180 } high { url 'url width 480 height 360 } ; it is not valid because unvisibleForThumbnails ; will be declared AFTER thumbnails unv 'unvisibleForThumbnails } unvisibleForThumbnails := nil myThumbnails 'thumbnails }
Замечание: в качестве имён биндингов можно использовать не только идентификаторы, но и строки.
6. Ссылки
Ссылки нужны для использования биндингов и их свойств в качестве значений. Ссылка на биндинг выглядит как одиночная кавычка и имя биндинга: 'myBinding, получение свойства выглядит так:
'myBinding.prop.foo.bar.baz
Так как справа находится atom, а не только id или string, свойства можно получать совершенно разные. Кроме того, свойства можно получать и у обычных выражений:
{ a ::= { b := 0 c ::= 5 c1 ? } d 'a ; {c 5} e a.c ; 5 f a.size ; 1 ? a.entries ; [[c 5] [c1 ?]] arr := [137 80 99] ; Quinacridone magenta zero arr.0 ; 137 arrSize [0 1 2].size }
Важно отметить, что у некоторых типов изначально есть свои свойства, находящиеся в свойстве props. Если это массив, то это его элементы по индексам, а также size. Если это мапа, то это entries, keys и values. Важно отметить, что при получении ссылки на свойство мапы будет браться именно пользовательский (или встроенный) биндинг, а не значение по ключу. Ещё раз:
{ ; m ; m = {key value} key value ; m = {key value}, m props are ; {entries [[key value]] keys [key] values [value]} prop := value ; m = {key value key1 value}, m props are ; {entries [[key value] [key1 value]] keys [key key1] values [value value]} key1 ::= value entries := 1 }
Если затенить встроенное свойство, то встроенная логика перестанет ра��отать (на самом деле, конечно, все эти entries, keys и size считаться будут, но вот выколупать их из значения будет уже невозможно, поскольку ссылка протухнет.
7. Int и Float
В JSON числа представлены единственным типом — number, Av же чётко проводит границу между целыми (при этом поддерживая шестнадцатеричные литералы) и дробными.
42.0 //number 42 //number
42.0 ; float 42 ; int #abcd ; hex int
8. Bool
Спецификация JSON определяет true, null и false как три литерала и не присваивает им какой‑либо тип, Av же настаивает, что true и false имеют тип bool. Выглядят они точно так же, как и в JSON, так что пример я приводить не буду.
9. Nil
Nil в Av отличается от null в JSON только тем, что дополнительно имеет трёхбуквенный вариант написания.
null
null ; JSON-compatible nil nil ; Av preferred nil
10. Atom
Av определяет общую категорию для неделимых значений и называет её atom. В atom входят:
Int;
Float;
String;
Bool;
Nil;
Id.
11. Атомы на месте ключей
В JSON ключом мапы может быть исключительно строка, Av позволяет использовать в качестве ключа значение любого типа, входящего в категорию atom.
{ "42": "answer", "answer": 42, "-0.0": "minusZero", "null": "val", "true": false, "false": true }
{ answer 42 42 answer -0.0 minusZero nil val true false false true }
12. Запятые
JSON не разрешает использовать замыкающие запятые (trailing commas) в конце списка или мапы, в Av это вполне возможно. Лично я не очень люблю их, однако вполне согласен с утверждением, что они очень полезны при активной вставке данных (не нужно постоянно писать запятую, чтобы вставить что‑то в конец).
Пример ниже взят отсюда.
[ { "name": "Chris", "age": 23, "city": "New York" }, { "name": "Emily", "age": 19, "city": "Atlanta" }, { "name": "Joe", "age": 32, "city": "New York" }, { "name": "Kevin", "age": 19, "city": "Atlanta" }, { "name": "Michelle", "age": 27, "city": "Los Angeles" }, { "name": "Robert", "age": 45, "city": "Manhattan" }, { "name": "Sarah", "age": 31, "city": "New York" } ]
[ { "name": "Chris", "age": 23, "city": "New York" }, { "name": "Emily", "age": 19, "city": "Atlanta" }, { "name": "Joe", "age": 32, "city": "New York" }, { "name": "Kevin", "age": 19, "city": "Atlanta" }, { "name": "Michelle", "age": 27, "city": "Los Angeles" }, { "name": "Robert", "age": 45, "city": "Manhattan" }, { "name": "Sarah", "age": 31, "city": "New York" }, ; note this trailing comma ]
Пример ниже сгенерирован с помощью этого сайта.
{ "result": [ "Oriental Concrete Tuna", "Gorgeous Plastic Pizza", "Handcrafted Soft Soap", "Fantastic Steel Computer", "Licensed Frozen Hat", "Generic Rubber Towels" ] }
{ "result": [ "Oriental Concrete Tuna", "Gorgeous Plastic Pizza", "Handcrafted Soft Soap", "Fantastic Steel Computer", "Licensed Frozen Hat", "Generic Rubber Towels", ; note this trailing comma ] }
13. Интерполяция строк
Av поддерживает интерполяцию строк, которая запускается после разбора текста на нём. Выглядит так же, как и в Cue:
{ answer := 42 "answer is \($answer)"; or just "answer is \(42)" }
Все выражения в Av могут быть приведены к строке, так что интерполяция относительно безопасна.
14. Опциональные фигурные скобки в топ-левеле
Av позволяет опускать фигурные скобки для мап в то��‑левеле, то есть когда в файле находится именно мапа.
// JSON allows only map at top-level // and only with curly braces { "foo": "bar", "baz": "quux", "arr": [0, 1, 2, 3] }
foo bar baz quux arr [0 1 2 3]
15. Семь смертных заимствований
В Av есть очень странная для языка конфигурации концепция — заимствования (borrow). Они позволяют брать два значения и определённым образом совмещать одно с другим, заимствуя у нового значения что‑то для старого. Вы можете думать о них как об операторах или инфиксных функциях (по факту так и есть). Для каждой пары типов есть своя перегрузка каждого заимствования.
Ниже приведён список встроенных заимствований:
Overlay:
~,overlayUnite:
+,uniteDefault:
|,defaultIntersect:
&,intersectDiffer:
‑,differEither:
^,eitherPush:
@,push
Ниже для каждого из заимствований я буду приводить табличку тип‑тип. Условные обозначения в табличке:
_— старое значение остаётся без изменений;l— левое значение;r— правое значение;?— Будет объяснено отдельно;lq— уникальность в пользу левого значения;rq— уникальность в пользу правого значения;[...arr]или{...obj}— spread, как в JavaScript.
Что ещё важно отметить: при сложении или вычитании чисел оба приводятся к старшему типу (int → float), а bool рассматривается как int (0 или 1). При сложении строки с чем‑либо происходит конкатенация. Единственный тип, почти не участвующий в заимствованиях, — это bytes.
Overlay (~)
Совмещает два значения, заменяя значения в первом объекте/массиве на соответствующие значения из второго, если они существуют. Если слева находится nil, то наложение срабатывает в пользу правого значения. Если слева массив, а справа — мапа, то мапа превращается в массив, получается
[[key1 val1] [key2 val2] ... [keyn valn]].
Если слева мапа, а справа — массив, массив превращается в мапу, получается
{0 el0 1 el1 ... n eln}.
~ | int | float | string | bool | nil | bytes | array | map |
int | _ | _ | _ | _ | _ | _ | _ | _ |
float | _ | _ | _ | _ | _ | _ | _ | _ |
string | _ | _ | _ | _ | _ | _ | _ | _ |
bool | _ | _ | _ | l xnor r | _ | _ | _ | _ |
nil | r | r | r | r | r | r | r | r |
bytes | _ | _ | _ | _ | _ | _ | _ | _ |
array | _ | _ | _ | _ | _ | _ | rq [...l, ...r] | rq [...l, ...r.arr] |
map | _ | _ | _ | _ | _ | _ | rq {...l, ...r.map} | rq {...l, ...r} |
object1 ::= { id 1 name "Object One" color red size large features [feature1 feature2] } object2 ::= { id 2 name "Object Two" color blue shape circle features [feature3 feature4] } ; object3_overlay 'object1 ~ 'object2 object3_overlay { id 2 name "Object Two" color blue size large shape circle features [feature3 feature4] }
Unite (+)
Объединяет два объекта или массива, добавляя значения из второго объекта/массива к первому без замены существующих значений.
+ | int | float | string | bool | nil | bytes | array | map |
int | l + r | l + r | l + r | l + r | _ | _ | _ | _ |
float | l + r | l + r | l + r | l + r | _ | _ | _ | _ |
string | l + r | l + r | l + r | l + r | _ | _ | _ | _ |
bool | l + r | l + r | l + r | l nor r | _ | _ | _ | _ |
nil | r | r | r | r | r | r | r | r |
bytes | _ | _ | _ | _ | _ | ? | _ | _ |
array | _ | _ | _ | _ | _ | _ | [...l, ...r] | [...l, ...r.arr] |
map | _ | _ | _ | _ | _ | _ | {...l, ...r.map} | {...l, ...r} |
Unite bytes‑bytes склеивает две последовательности байтов.
; object3_unite 'object1 + 'object2 object3_unite { id 2 name "Object One" color red size large shape circle features [feature1 feature2 feature3 feature4] }
Default (|)
Применяет значения из второго объекта или массива к первому, только если они отсутствуют в первом.
| | int | float | string | bool | nil | bytes | array | map |
int | _ | _ | _ | _ | _ | _ | _ | _ |
float | _ | _ | _ | _ | _ | _ | _ | _ |
string | _ | _ | _ | _ | _ | _ | _ | _ |
bool | _ | _ | _ | l or r | _ | _ | _ | _ |
nil | r | r | r | r | r | r | r | r |
bytes | _ | _ | _ | _ | _ | _ | _ | _ |
array | _ | _ | _ | _ | _ | _ | lq [...l, ...r] | lq [...l, ...r.arr] |
map | _ | _ | _ | _ | _ | _ | lq {...l, ...r.map} | lq {...l, ...r} |
; object3_default 'object1 | 'object2 object3_default { id 1 name "Object One" color red size large shape circle features [feature1 feature2] }
Intersect (&)
Создает новый объект или массив, содержащий только те значения, которые присутствуют в обоих объектах или массивах. Конфликты разрешаются в пользу левого значения.
& | int | float | string | bool | nil | bytes | array | map |
int | _ | _ | _ | _ | _ | _ | _ | _ |
float | _ | _ | _ | _ | _ | _ | _ | _ |
string | _ | _ | _ | _ | _ | _ | _ | _ |
bool | _ | _ | _ | l and r | _ | _ | _ | _ |
nil | r | r | r | r | r | r | r | r |
bytes | _ | _ | _ | _ | _ | _ | _ | _ |
array | _ | _ | _ | _ | _ | _ | common [...l, ...r] | common [...l, ...r.arr] |
map | _ | _ | _ | _ | _ | _ | common {...l, ...r.map} | common {...l, ...r} |
; object3_intersect 'object1 & 'object2 object3_intersect { id 1 name "Object One" color red features [feature1 feature2] }
Differ (-)
Удаляет из первого объекта или массива значения, которые присутствуют во втором.
- | int | float | string | bool | nil | bytes | array | map |
int | l - r | l - r | _ | l - r | _ | _ | _ | _ |
float | l - r | l - r | _ | l - r | _ | _ | _ | _ |
string | _ | _ | _ | _ | _ | _ | _ | _ |
bool | l - r | l - r | _ | l nand r | _ | _ | _ | _ |
nil | r | r | r | r | r | r | r | r |
bytes | _ | _ | _ | _ | _ | _ | _ | _ |
array | _ | _ | _ | _ | _ | _ | diff [...l, ...r] | diff [...l, ...r.arr] |
map | _ | _ | _ | _ | _ | _ | diff {...l, ...r.map} | diff {...l, ...r} |
; object3_differ 'object1 - 'object2 object3_differ { size large }
Either (^)
Объединяет два объекта или массива, выбирая значения, присутствующие только в одном из них, но не в обоих.
^ | int | float | string | bool | nil | bytes | array | map |
int | _ | _ | _ | _ | _ | _ | _ | _ |
float | _ | _ | _ | _ | _ | _ | _ | _ |
string | _ | _ | _ | _ | _ | _ | _ | _ |
bool | _ | _ | _ | l xor r | _ | _ | _ | _ |
nil | r | r | r | r | r | r | r | r |
bytes | _ | _ | _ | _ | _ | _ | _ | _ |
array | _ | _ | _ | _ | _ | _ | unique [...l, ...r] | unique [...l, ...r.arr] |
map | _ | _ | _ | _ | _ | _ | unique {...l, ...r.map} | unique {...l, ...r} |
; object3_either 'object1 ^ 'object2 object3_either { size large shape circle }
Push (@)
Добавляет значения из второго объекта или массива к первому независимо от того, существуют ли они уже в первом.
@ | int | float | string | bool | nil | bytes | array | map |
int | _ | _ | _ | _ | _ | _ | _ | _ |
float | _ | _ | _ | _ | _ | _ | _ | _ |
string | _ | _ | _ | _ | _ | _ | _ | _ |
bool | _ | _ | _ | l imply r | _ | _ | _ | _ |
nil | r | r | r | r | r | r | r | r |
bytes | _ | _ | _ | _ | _ | _ | _ | _ |
array | [...l, r] | [...l, r] | [...l, r] | [...l, r] | [...l, r] | [...l, r] | [...l, r] | [...l, r] |
map | {...l, r} | {...l, r} | {...l, r} | {...l, r} | {...l, r} | {...l, r} | {...l, r} | {...l, r} |
; object3_push 'object1 @ 'object2 ;object3_push { ; id 1 ; name "Object One" ; color red ; size large ; features [feature1 feature2] ; { ; wont't work: there's no key ; id 2 ; name "Object Two" ; color blue ; shape circle ; features [feature3 feature4] ; } ;} ; object3_push 'object1 ~ { ; features 'object1.features @ 'object2.features ; } object3_push { id 1 name "Object One" color red size large features [feature1 feature2 [feature3 feature4]] }
Сжимаем JSON в идиоматичный Av
Шаг 0. JSON
Ниже приведён ответ YouTube на некий запрос (код взят отсюда). Ответ, конечно же, в формате JSON. Ответ пропущен через форматтер с шириной табуляции в два пробела, фигурные скобки расположены, как обычно (далее все примеры Av будут отформатированы аналогичным образом).
5,987 символов
{ "kind": "youtube#searchListResponse", "etag": "q4ibjmYp1KA3RqMF4jFLl6PBwOg", "nextPageToken": "CAUQAA", "regionCode": "NL", "pageInfo": { "totalResults": 1000000, "resultsPerPage": 5 }, "items": [ { "kind": "youtube#searchResult", "etag": "QCsHBifbaernVCbLv8Cu6rAeaDQ", "id": { "kind": "youtube#video", "videoId": "TvWDY4Mm5GM" }, "snippet": { "publishedAt": "2023-07-24T14:15:01Z", "channelId": "UCwozCpFp9g9x0wAzuFh0hwQ", "title": "3 Football Clubs Kylian Mbappe Should Avoid Signing ✍️❌⚽️ #football #mbappe #shorts", "description": "", "thumbnails": { "default": { "url": "https://i.ytimg.com/vi/TvWDY4Mm5GM/default.jpg", "width": 120, "height": 90 }, "medium": { "url": "https://i.ytimg.com/vi/TvWDY4Mm5GM/mqdefault.jpg", "width": 320, "height": 180 }, "high": { "url": "https://i.ytimg.com/vi/TvWDY4Mm5GM/hqdefault.jpg", "width": 480, "height": 360 } }, "channelTitle": "FC Motivate", "liveBroadcastContent": "none", "publishTime": "2023-07-24T14:15:01Z" } }, { "kind": "youtube#searchResult", "etag": "0NG5QHdtIQM_V-DBJDEf-jK_Y9k", "id": { "kind": "youtube#video", "videoId": "aZM_42CcNZ4" }, "snippet": { "publishedAt": "2023-07-24T16:09:27Z", "channelId": "UCM5gMM_HqfKHYIEJ3lstMUA", "title": "Which Football Club Could Cristiano Ronaldo Afford To Buy? 💰", "description": "Sign up to Sorare and get a FREE card: https://sorare.pxf.io/NellisShorts Give Soraredata a go for FREE: ...", "thumbnails": { "default": { "url": "https://i.ytimg.com/vi/aZM_42CcNZ4/default.jpg", "width": 120, "height": 90 }, "medium": { "url": "https://i.ytimg.com/vi/aZM_42CcNZ4/mqdefault.jpg", "width": 320, "height": 180 }, "high": { "url": "https://i.ytimg.com/vi/aZM_42CcNZ4/hqdefault.jpg", "width": 480, "height": 360 } }, "channelTitle": "John Nellis", "liveBroadcastContent": "none", "publishTime": "2023-07-24T16:09:27Z" } }, { "kind": "youtube#searchResult", "etag": "WbBz4oh9I5VaYj91LjeJvffrBVY", "id": { "kind": "youtube#video", "videoId": "wkP3XS3aNAY" }, "snippet": { "publishedAt": "2023-07-24T16:00:50Z", "channelId": "UC4EP1dxFDPup_aFLt0ElsDw", "title": "PAULO DYBALA vs THE WORLD'S LONGEST FREEKICK WALL", "description": "Can Paulo Dybala curl a football around the World's longest free kick wall? We met up with the World Cup winner and put him to ...", "thumbnails": { "default": { "url": "https://i.ytimg.com/vi/wkP3XS3aNAY/default.jpg", "width": 120, "height": 90 }, "medium": { "url": "https://i.ytimg.com/vi/wkP3XS3aNAY/mqdefault.jpg", "width": 320, "height": 180 }, "high": { "url": "https://i.ytimg.com/vi/wkP3XS3aNAY/hqdefault.jpg", "width": 480, "height": 360 } }, "channelTitle": "Shoot for Love", "liveBroadcastContent": "none", "publishTime": "2023-07-24T16:00:50Z" } }, { "kind": "youtube#searchResult", "etag": "juxv_FhT_l4qrR05S1QTrb4CGh8", "id": { "kind": "youtube#video", "videoId": "rJkDZ0WvfT8" }, "snippet": { "publishedAt": "2023-07-24T10:00:39Z", "channelId": "UCO8qj5u80Ga7N_tP3BZWWhQ", "title": "TOP 10 DEFENDERS 2023", "description": "SoccerKingz https://soccerkingz.nl Use code: 'ILOVEHOF' to get 10% off. TOP 10 DEFENDERS 2023 Follow us! • Instagram ...", "thumbnails": { "default": { "url": "https://i.ytimg.com/vi/rJkDZ0WvfT8/default.jpg", "width": 120, "height": 90 }, "medium": { "url": "https://i.ytimg.com/vi/rJkDZ0WvfT8/mqdefault.jpg", "width": 320, "height": 180 }, "high": { "url": "https://i.ytimg.com/vi/rJkDZ0WvfT8/hqdefault.jpg", "width": 480, "height": 360 } }, "channelTitle": "Home of Football", "liveBroadcastContent": "none", "publishTime": "2023-07-24T10:00:39Z" } }, { "kind": "youtube#searchResult", "etag": "wtuknXTmI1txoULeH3aWaOuXOow", "id": { "kind": "youtube#video", "videoId": "XH0rtu4U6SE" }, "snippet": { "publishedAt": "2023-07-21T16:30:05Z", "channelId": "UCwozCpFp9g9x0wAzuFh0hwQ", "title": "3 Things You Didn't Know About Erling Haaland ⚽️🇳🇴 #football #haaland #shorts", "description": "", "thumbnails": { "default": { "url": "https://i.ytimg.com/vi/XH0rtu4U6SE/default.jpg", "width": 120, "height": 90 }, "medium": { "url": "https://i.ytimg.com/vi/XH0rtu4U6SE/mqdefault.jpg", "width": 320, "height": 180 }, "high": { "url": "https://i.ytimg.com/vi/XH0rtu4U6SE/hqdefault.jpg", "width": 480, "height": 360 } }, "channelTitle": "FC Motivate", "liveBroadcastContent": "none", "publishTime": "2023-07-21T16:30:05Z" } } ] }
Шаг 1. Первичный марафет
На первом шаге я:
Заменяю строки идентификаторами там, где это возможно;
Убираю двоеточия после ключей;
Убираю запятые;
Опускаю фигурные скобки в топ‑левеле.
5,069 символов
kind "youtube#searchListResponse" etag q4ibjmYp1KA3RqMF4jFLl6PBwOg nextPageToken: CAUQAA regionCode NL pageInfo { totalResults 1000000 resultsPerPage 5 } items [ { kind "youtube#searchResult" etag QCsHBifbaernVCbLv8Cu6rAeaDQ id { kind "youtube#video" videoId TvWDY4Mm5GM } snippet { publishedAt "2023-07-24T14:15:01Z" channelId UCwozCpFp9g9x0wAzuFh0hwQ title "3 Football Clubs Kylian Mbappe Should Avoid Signing ✍️❌⚽️ #football #mbappe #shorts" description "" thumbnails { "default" { url "https://i.ytimg.com/vi/TvWDY4Mm5GM/default.jpg" width 120 height 90 } medium { url "https://i.ytimg.com/vi/TvWDY4Mm5GM/mqdefault.jpg" width 320 height 180 } high { url "https://i.ytimg.com/vi/TvWDY4Mm5GM/hqdefault.jpg" width 480 height 360 } } channelTitle "FC Motivate" liveBroadcastContent none publishTime "2023-07-24T14:15:01Z" } } { kind "youtube#searchResult" etag 0NG5QHdtIQM_V-DBJDEf-jK_Y9k id { kind "youtube#video" videoId aZM_42CcNZ4 } snippet { publishedAt "2023-07-24T16:09:27Z" channelId UCM5gMM_HqfKHYIEJ3lstMUA title "Which Football Club Could Cristiano Ronaldo Afford To Buy? 💰" description "Sign up to Sorare and get a FREE card: https://sorare.pxf.io/NellisShorts Give Soraredata a go for FREE: ..." thumbnails { "default" { url "https://i.ytimg.com/vi/aZM_42CcNZ4/default.jpg" width 120 height 90 } medium { url "https://i.ytimg.com/vi/aZM_42CcNZ4/mqdefault.jpg" width 320 height 180 } high { url "https://i.ytimg.com/vi/aZM_42CcNZ4/hqdefault.jpg" width 480 height 360 } } channelTitle "John Nellis" liveBroadcastContent none publishTime "2023-07-24T16:09:27Z" } } { kind "youtube#searchResult" etag WbBz4oh9I5VaYj91LjeJvffrBVY id { kind "youtube#video" videoId wkP3XS3aNAY } snippet { publishedAt "2023-07-24T16:00:50Z" channelId UC4EP1dxFDPup_aFLt0ElsDw title "PAULO DYBALA vs THE WORLD'S LONGEST FREEKICK WALL" description "Can Paulo Dybala curl a football around the World's longest free kick wall? We met up with the World Cup winner and put him to ..." thumbnails { "default" { url "https://i.ytimg.com/vi/wkP3XS3aNAY/default.jpg" width 120 height 90 } medium { url "https://i.ytimg.com/vi/wkP3XS3aNAY/mqdefault.jpg" width 320 height 180 } high { url "https://i.ytimg.com/vi/wkP3XS3aNAY/hqdefault.jpg" width 480 height 360 } } channelTitle "Shoot for Love" liveBroadcastContent none publishTime "2023-07-24T16:00:50Z" } } { kind "youtube#searchResult" etag juxv_FhT_l4qrR05S1QTrb4CGh8 id { kind "youtube#video" videoId rJkDZ0WvfT8 } snippet { publishedAt "2023-07-24T10:00:39Z" channelId UCO8qj5u80Ga7N_tP3BZWWhQ title "TOP 10 DEFENDERS 2023" description "SoccerKingz https://soccerkingz.nl Use code: 'ILOVEHOF' to get 10% off. TOP 10 DEFENDERS 2023 Follow us! • Instagram ..." thumbnails { "default" { url "https://i.ytimg.com/vi/rJkDZ0WvfT8/default.jpg" width 120 height 90 } medium { url "https://i.ytimg.com/vi/rJkDZ0WvfT8/mqdefault.jpg" width 320 height 180 } high { url "https://i.ytimg.com/vi/rJkDZ0WvfT8/hqdefault.jpg" width 480 height 360 } } channelTitle "Home of Football" liveBroadcastContent none publishTime "2023-07-24T10:00:39Z" } } { kind "youtube#searchResult" etag "wtuknXTmI1txoULeH3aWaOuXOow" id { kind "youtube#video" videoId XH0rtu4U6SE } snippet { publishedAt "2023-07-21T16:30:05Z" channelId UCwozCpFp9g9x0wAzuFh0hwQ title "3 Things You Didn't Know About Erling Haaland ⚽️🇳🇴 #football #haaland #shorts" description "" thumbnails { "default" { url "https://i.ytimg.com/vi/XH0rtu4U6SE/default.jpg" width 120 height 90 } medium { url "https://i.ytimg.com/vi/XH0rtu4U6SE/mqdefault.jpg" width 320 height 180 } high { url "https://i.ytimg.com/vi/XH0rtu4U6SE/hqdefault.jpg" width 480 height 360 } } channelTitle "FC Motivate" liveBroadcastContent none publishTime "2023-07-21T16:30:05Z" } } ]
Результат первого шага: 84.7% от шага 0.
Шаг 2. Вынос thumbnails в биндинги
5,335 символов
thumbnails := { url := "https://i.ytimg.com/vi/" "default" ::= { url ::= 'url width 120 height 90 } medium ::= { url ::= 'url width 320 height 180 } high ::= { url ::= 'url width 480 height 360 } } kind "youtube#searchListResponse" etag q4ibjmYp1KA3RqMF4jFLl6PBwOg nextPageToken: CAUQAA regionCode NL pageInfo { totalResults 1000000 resultsPerPage 5 } items [ { kind "youtube#searchResult" etag QCsHBifbaernVCbLv8Cu6rAeaDQ id { kind "youtube#video" videoId TvWDY4Mm5GM } snippet { publishedAt "2023-07-24T14:15:01Z" channelId UCwozCpFp9g9x0wAzuFh0hwQ title "3 Football Clubs Kylian Mbappe Should Avoid Signing ✍️❌⚽️ #football #mbappe #shorts" description "" thumbnails { "default" { url "https://i.ytimg.com/vi/TvWDY4Mm5GM/default.jpg" width 120 height 90 } medium { url "https://i.ytimg.com/vi/TvWDY4Mm5GM/mqdefault.jpg" width 320 height 180 } high { url "https://i.ytimg.com/vi/TvWDY4Mm5GM/hqdefault.jpg" width 480 height 360 } } channelTitle "FC Motivate" liveBroadcastContent none publishTime "2023-07-24T14:15:01Z" } } { kind "youtube#searchResult" etag 0NG5QHdtIQM_V-DBJDEf-jK_Y9k id { kind "youtube#video" videoId aZM_42CcNZ4 } snippet { publishedAt "2023-07-24T16:09:27Z" channelId UCM5gMM_HqfKHYIEJ3lstMUA title "Which Football Club Could Cristiano Ronaldo Afford To Buy? 💰" description "Sign up to Sorare and get a FREE card: https://sorare.pxf.io/NellisShorts Give Soraredata a go for FREE: ..." thumbnails { "default" { url "https://i.ytimg.com/vi/aZM_42CcNZ4/default.jpg" width 120 height 90 } medium { url "https://i.ytimg.com/vi/aZM_42CcNZ4/mqdefault.jpg" width 320 height 180 } high { url "https://i.ytimg.com/vi/aZM_42CcNZ4/hqdefault.jpg" width 480 height 360 } } channelTitle "John Nellis" liveBroadcastContent none publishTime "2023-07-24T16:09:27Z" } } { kind "youtube#searchResult" etag WbBz4oh9I5VaYj91LjeJvffrBVY id { kind "youtube#video" videoId wkP3XS3aNAY } snippet { publishedAt "2023-07-24T16:00:50Z" channelId UC4EP1dxFDPup_aFLt0ElsDw title "PAULO DYBALA vs THE WORLD'S LONGEST FREEKICK WALL" description "Can Paulo Dybala curl a football around the World's longest free kick wall? We met up with the World Cup winner and put him to ..." thumbnails { "default" { url "https://i.ytimg.com/vi/wkP3XS3aNAY/default.jpg" width 120 height 90 } medium { url "https://i.ytimg.com/vi/wkP3XS3aNAY/mqdefault.jpg" width 320 height 180 } high { url "https://i.ytimg.com/vi/wkP3XS3aNAY/hqdefault.jpg" width 480 height 360 } } channelTitle "Shoot for Love" liveBroadcastContent none publishTime "2023-07-24T16:00:50Z" } } { kind "youtube#searchResult" etag juxv_FhT_l4qrR05S1QTrb4CGh8 id { kind "youtube#video" videoId rJkDZ0WvfT8 } snippet { publishedAt "2023-07-24T10:00:39Z" channelId UCO8qj5u80Ga7N_tP3BZWWhQ title "TOP 10 DEFENDERS 2023" description "SoccerKingz https://soccerkingz.nl Use code: 'ILOVEHOF' to get 10% off. TOP 10 DEFENDERS 2023 Follow us! • Instagram ..." thumbnails { "default" { url "https://i.ytimg.com/vi/rJkDZ0WvfT8/default.jpg" width 120 height 90 } medium { url "https://i.ytimg.com/vi/rJkDZ0WvfT8/mqdefault.jpg" width 320 height 180 } high { url "https://i.ytimg.com/vi/rJkDZ0WvfT8/hqdefault.jpg" width 480 height 360 } } channelTitle "Home of Football" liveBroadcastContent none publishTime "2023-07-24T10:00:39Z" } } { kind "youtube#searchResult" etag "wtuknXTmI1txoULeH3aWaOuXOow" id { kind "youtube#video" videoId XH0rtu4U6SE } snippet { publishedAt "2023-07-21T16:30:05Z" channelId UCwozCpFp9g9x0wAzuFh0hwQ title "3 Things You Didn't Know About Erling Haaland ⚽️🇳🇴 #football #haaland #shorts" description "" thumbnails { "default" { url "https://i.ytimg.com/vi/XH0rtu4U6SE/default.jpg" width 120 height 90 } medium { url "https://i.ytimg.com/vi/XH0rtu4U6SE/mqdefault.jpg" width 320 height 180 } high { url "https://i.ytimg.com/vi/XH0rtu4U6SE/hqdefault.jpg" width 480 height 360 } } channelTitle "FC Motivate" liveBroadcastContent none publishTime "2023-07-21T16:30:05Z" } } ]
Результат второго шага: 105.3% от шага 1, 89.1% от шага 0.
Шаг 3. Использование borrow и интерполированных строк
4,551 символ
thumbnails := { url := "https://i.ytimg.com/vi/" "default" ::= { url ::= 'url width 120 height 90 } medium ::= { url ::= 'url width 320 height 180 } high ::= { url ::= 'url width 480 height 360 } } kind "youtube#searchListResponse" etag q4ibjmYp1KA3RqMF4jFLl6PBwOg nextPageToken: CAUQAA regionCode NL pageInfo { totalResults 1000000 resultsPerPage 5 } items [ url := 'thumbnails.url { kind "youtube#searchResult" etag QCsHBifbaernVCbLv8Cu6rAeaDQ id { kind "youtube#video" videoId TvWDY4Mm5GM } snippet { publishedAt "2023-07-24T14:15:01Z" channelId UCwozCpFp9g9x0wAzuFh0hwQ title "3 Football Clubs Kylian Mbappe Should Avoid Signing ✍️❌⚽️ #football #mbappe #shorts" description "" thumbnails 'thumbnails ~ { "default" { url "\('url)TvWDY4Mm5GM/default.jpg" } medium { url "\('url)TvWDY4Mm5GM/mqdefault.jpg" } high { url "\('url)TvWDY4Mm5GM/hqdefault.jpg" } } channelTitle "FC Motivate" liveBroadcastContent none publishTime "2023-07-24T14:15:01Z" } } { kind "youtube#searchResult" etag 0NG5QHdtIQM_V-DBJDEf-jK_Y9k id { kind "youtube#video" videoId aZM_42CcNZ4 } snippet { publishedAt "2023-07-24T16:09:27Z" channelId UCM5gMM_HqfKHYIEJ3lstMUA title "Which Football Club Could Cristiano Ronaldo Afford To Buy? 💰" description "Sign up to Sorare and get a FREE card: https://sorare.pxf.io/NellisShorts Give Soraredata a go for FREE: ..." thumbnails 'thumbnails ~ { "default" { url "\('url)aZM_42CcNZ4/default.jpg" } medium { url "\('url)aZM_42CcNZ4/mqdefault.jpg" } high { url "\('url)aZM_42CcNZ4/hqdefault.jpg" } } channelTitle "John Nellis" liveBroadcastContent none publishTime "2023-07-24T16:09:27Z" } } { kind "youtube#searchResult" etag WbBz4oh9I5VaYj91LjeJvffrBVY id { kind "youtube#video" videoId wkP3XS3aNAY } snippet { publishedAt "2023-07-24T16:00:50Z" channelId UC4EP1dxFDPup_aFLt0ElsDw title "PAULO DYBALA vs THE WORLD'S LONGEST FREEKICK WALL" description "Can Paulo Dybala curl a football around the World's longest free kick wall? We met up with the World Cup winner and put him to ..." thumbnails 'thumbnails ~ { "default" { url "\('url)wkP3XS3aNAY/default.jpg" } medium { url "\('url)wkP3XS3aNAY/mqdefault.jpg" } high { url "\('url)wkP3XS3aNAY/hqdefault.jpg" } } channelTitle "Shoot for Love" liveBroadcastContent none publishTime "2023-07-24T16:00:50Z" } } { kind "youtube#searchResult" etag juxv_FhT_l4qrR05S1QTrb4CGh8 id { kind "youtube#video" videoId rJkDZ0WvfT8 } snippet { publishedAt "2023-07-24T10:00:39Z" channelId UCO8qj5u80Ga7N_tP3BZWWhQ title "TOP 10 DEFENDERS 2023" description "SoccerKingz https://soccerkingz.nl Use code: 'ILOVEHOF' to get 10% off. TOP 10 DEFENDERS 2023 Follow us! • Instagram ..." thumbnails 'thumbnails ~ { "default" { url "\('url)rJkDZ0WvfT8/default.jpg" } medium { url "\('url)rJkDZ0WvfT8/mqdefault.jpg" } high { url "\('url)rJkDZ0WvfT8/hqdefault.jpg" } } channelTitle "Home of Football" liveBroadcastContent none publishTime "2023-07-24T10:00:39Z" } } { kind "youtube#searchResult" etag "wtuknXTmI1txoULeH3aWaOuXOow" id { kind "youtube#video" videoId XH0rtu4U6SE } snippet { publishedAt "2023-07-21T16:30:05Z" channelId UCwozCpFp9g9x0wAzuFh0hwQ title "3 Things You Didn't Know About Erling Haaland ⚽️🇳🇴 #football #haaland #shorts" description "" thumbnails 'thumbnails ~ { "default" { url "\('url)XH0rtu4U6SE/default.jpg" } medium { url "\('url)XH0rtu4U6SE/mqdefault.jpg" } high { url "\('url)XH0rtu4U6SE/hqdefault.jpg" } } channelTitle "FC Motivate" liveBroadcastContent none publishTime "2023-07-21T16:30:05Z" } } ]
Результат третьего шага: 82.2% от шага 2, 76.0% от шага 0.
Шаг 4. Дополнительные сокращения
4,440 символов
thumbnails := { url := "https://i.ytimg.com/vi/" "default" ::= { width 120 height 90 } medium ::= { width 320 height 180 } high ::= { width 480 height 360 } } kind "youtube#searchListResponse" etag q4ibjmYp1KA3RqMF4jFLl6PBwOg nextPageToken: CAUQAA regionCode NL pageInfo { totalResults 1000000 resultsPerPage 5 } items [ url := 'thumbnails.url searchResult := { kind "youtube#searchResult" id ::= { kind "youtube#video" } snippet ::= { description "" liveBroadcastContent none } } 'searchResult ~ { etag QCsHBifbaernVCbLv8Cu6rAeaDQ id 'searchResult.id + { videoId TvWDY4Mm5GM } snippet 'searchResult.snippet ~ { p := "2023-07-24T14:15:01Z" publishedAt 'p channelId UCwozCpFp9g9x0wAzuFh0hwQ title "3 Football Clubs Kylian Mbappe Should Avoid Signing ✍️❌⚽️ #football #mbappe #shorts" thumbnails 'thumbnails ~ { "default" { url "\('url)TvWDY4Mm5GM/default.jpg" } medium { url "\('url)TvWDY4Mm5GM/mqdefault.jpg" } high { url "\('url)TvWDY4Mm5GM/hqdefault.jpg" } } channelTitle "FC Motivate" publishTime 'p } } 'searchResult ~ { etag 0NG5QHdtIQM_V-DBJDEf-jK_Y9k id 'searchResult.id + { videoId aZM_42CcNZ4 } snippet 'searchResult.snippet ~ { p := "2023-07-24T16:09:27Z" publishedAt 'p channelId UCM5gMM_HqfKHYIEJ3lstMUA title "Which Football Club Could Cristiano Ronaldo Afford To Buy? 💰" description "Sign up to Sorare and get a FREE card: https://sorare.pxf.io/NellisShorts Give Soraredata a go for FREE: ..." thumbnails 'thumbnails ~ { "default" { url "\('url)aZM_42CcNZ4/default.jpg" } medium { url "\('url)aZM_42CcNZ4/mqdefault.jpg" } high { url "\('url)aZM_42CcNZ4/hqdefault.jpg" } } channelTitle "John Nellis" publishTime 'p } } 'searchResult ~ { etag WbBz4oh9I5VaYj91LjeJvffrBVY id 'searchResult.id + { videoId wkP3XS3aNAY } snippet 'searchResult.snippet ~ { p := "2023-07-24T16:00:50Z" publishedAt 'p channelId UC4EP1dxFDPup_aFLt0ElsDw title "PAULO DYBALA vs THE WORLD'S LONGEST FREEKICK WALL" description "Can Paulo Dybala curl a football around the World's longest free kick wall? We met up with the World Cup winner and put him to ..." thumbnails 'thumbnails ~ { "default" { url "\('url)wkP3XS3aNAY/default.jpg" } medium { url "\('url)wkP3XS3aNAY/mqdefault.jpg" } high { url "\('url)wkP3XS3aNAY/hqdefault.jpg" } } channelTitle "Shoot for Love" publishTime 'p } } 'searchResult ~ { etag juxv_FhT_l4qrR05S1QTrb4CGh8 id 'searchResult.id + { videoId rJkDZ0WvfT8 } snippet 'searchResult.snippet ~ { p := "2023-07-24T10:00:39Z" publishedAt 'p channelId UCO8qj5u80Ga7N_tP3BZWWhQ title "TOP 10 DEFENDERS 2023" description "SoccerKingz https://soccerkingz.nl Use code: 'ILOVEHOF' to get 10% off. TOP 10 DEFENDERS 2023 Follow us! • Instagram ..." thumbnails 'thumbnails ~ { "default" { url "\('url)rJkDZ0WvfT8/default.jpg" } medium { url "\('url)rJkDZ0WvfT8/mqdefault.jpg" } high { url "\('url)rJkDZ0WvfT8/hqdefault.jpg" } } channelTitle "Home of Football" publishTime 'p } } 'searchResult ~ { etag wtuknXTmI1txoULeH3aWaOuXOow id 'searchResult.id + { videoId XH0rtu4U6SE } snippet 'searchResult.snippet ~ { p := "2023-07-21T16:30:05Z" publishedAt 'p channelId UCwozCpFp9g9x0wAzuFh0hwQ title "3 Things You Didn't Know About Erling Haaland ⚽️🇳🇴 #football #haaland #shorts" thumbnails 'thumbnails ~ { "default" { url "\('url)XH0rtu4U6SE/default.jpg" } medium { url "\('url)XH0rtu4U6SE/mqdefault.jpg" } high { url "\('url)XH0rtu4U6SE/hqdefault.jpg" } } channelTitle "FC Motivate" publishTime 'p } } ]
Результат третьего шага: 97.6% от шага 2, 74.2% от шага 0.
Сжатие минифицированного JSON
Сравним компактные версии JSON и Av:
{"name":"Chris","age":23,"address":{"city":"New York","country":"America"},"friends":[{"name":"Emily","hobbies":["biking","music","gaming"]},{"name":"John","hobbies":["soccer","gaming"]}]}
{name Chris age 23 address{city"New York"country America}friends[{name Emily hobbies[biking music gaming]}{name John hobbies[soccer gaming]}]}
JSON — 188 символов (100.0%), Av — 142 символа (75.5%).
Итог
Вот такой странный язык конфигурации я написал для своего движка. Мог бы я обойтись без него? Да, конечно, ведь есть стабильные альтернативы вроде Cue, Jsonnet, Dhall. В крайнем случае можно было бы продолжать использовать JSON. Зачем я это сделал? Ну, я люблю придумывать языки с нескучным синтаксисом, а тут был слишком удобный предлог, чтобы я мог его проигнорировать. Нужен ли Av вам? Я думаю, нет. Это локальный язычок, который удовлетворяет всем требованиям своего создателя и только него. Тулинга для не��о нет, статей и туториалов по нему нет (ну, кроме этой). Официальной спецификации у него нет, семантика довольно странная. Я бы даже сказал, что он больше похож на новый эзотерический язык программирования, чем на реальную замену JSON. Ну и, конечно же, самый главный отпугивающий фактор - я очень сильно перемудрил с языком. Если честно, изначально в Av были только три пункта из списка опциональных нововведений и новые типы (int, float, bool, bytes). Всё остальное я добавлял в процессе разработки.
Однако если вам Av всё же показался интересным, в следующей части могу разобрать, как устроены кастомные заимствования, лямбды и сопоставление с образцом в Av. Они позволяют, пусть и ограниченно, программировать на Av.
