Здравствуйте, судя по моему скромному опыту, с таким форматом обменом данными, как 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:
~
,overlay
Unite:
+
,unite
Default:
|
,default
Intersect:
&
,intersect
Differ:
‑
,differ
Either:
^
,either
Push:
@
,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.