Здравствуйте, судя по моему скромному опыту, с таким форматом обменом данными, как 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.
Комментарии (26)
ruomserg
30.07.2024 15:02+6О! Еще одно подтверждение - любой достаточно продвинутый язык конфигурации стремится стать эквивалентом машине тьюринга, и превратиться в язык программирования без адекватных инструментальных средств, проверки синтаксиса, отладчика, но с комьюнити из целых нескольких человек... :-)
IIvana
30.07.2024 15:02; object3_unite 'object1 + 'object2 object3_unite { id 2 name "Object One" color red
поручик, а почему не объединились числа, строки и идентификаторы?
раскладец, батенька, раскладец! (С)
delphinpro
30.07.2024 15:02+2К чему хотелось бы придраться =)
Убрал двоеточие и запятые - если опционально то ок. Иначе зря.
Про запятые говорю те, которые пишутся в столбик (как в sass необязательные ";" в конце свойств). Допустимая запятая после последнего ключа - ок.
кстати, если убрать еще и фигурные скобки, то получится почти yaml =)
Ну и главное - для удобного использования нужны плагины для популярных IDE, f их нет, насколько я понимаю, раз уж сам парсер даже в открытом доступе не присутствует.
ну а так в целом - интересно. на плюсик тянет =)Fancryer Автор
30.07.2024 15:02Да, двоеточия и запятые можно опускать по желанию, любой JSON - это валидный Av. Можно даже смешивать и где-то писать их, а где-то - нет. Мне просто не нравится, как выглядит смешанный вариант, вот и не привёл такой пример.
delphinpro
30.07.2024 15:02Если говорить об альтернативных форматах json, то лично для меня основной стоп-фактор их использования - отсутствие поддержки в IDE. Иногда хотелось бы использовать в качестве конфига и при этом не заморачиваться с лишними кавычками (в ключах) и добавлять комментарии. Пакетов полно. Но открываешь такой файл в редакторе и он весь красный. Думаешь - да в *опу, заюзаю конфиг в виде php массива.
Anarchist
30.07.2024 15:02HOCON уже видели? :)
Fancryer Автор
30.07.2024 15:02+2Да, видел. Может быть, я ошибаюсь и это на самом деле круто, но мне не очень нравится идея настолько неявной конкатенации строк, массивов и объектов. Плюс там пробелы и переносы строк гораздо важнее, чем в JSON или Av, что мне тоже не очень нравится. Пример ниже (из документации HOCON) как раз демонстрирует то, что мне не приглянулось.
// this is an array with one element, the string "1 2 3 4" [ 1 2 3 4 ] // this is an array of four integers [ 1 2 3 4 ] // an array of one element, the array [ 1, 2, 3, 4 ] [ [ 1, 2 ] [ 3, 4 ] ] // an array of two arrays [ [ 1, 2 ] [ 3, 4 ] ]
Anarchist
30.07.2024 15:02Любой JSON - валидный HOCON, так что пробелы и переносы строк там важны только в случаях, когда используется только сокращенный синтаксис без кавычек. Но в HOCON ещё и другие интересные фишки: включения документов, reference.conf, ccылки на иные параметры. Ваша попытка создать шестнадцатый стандарт интересна, конечно, но пока укладывается в картинку https://imgs.xkcd.com/comics/standards.png
Но серьезно, достойно уважения. Я бы присмотрелся скорее к YAML и сделал что-то похожее на него в духе переделки JSON в HOCON. В конце концов для конфигов более всего важна человекочитабельность.
SquareRootOfZero
30.07.2024 15:02Пару лет назад писал программу, использовавшую очень большие и развесистые конфигурационные файлы - сперва эти файлы были в JSON, но возможности закомментировать часть файла очень не хватало, переделал на YAML, тем более его как-то в целом удобнее было руками редактировать. Через какое-то время стало не хватать возможности, не повторяясь, описывать структуру вложенных директорий, вместо того, чтобы к каждому файлу данных прописывать полный путь (YAML позволяет расширять парсер, дописал сам), как-то типа так:
- myfolder/ -- somefile.abc -- anotherfolder/ -- -- anotherfile.xyz
Не помню уже синтаксис ни своей писанины, ни даже самого формата YAML, но идея, думаю, понятна. Ваш формат не умеет что-то такое "из коробки"?
AlexMih
30.07.2024 15:02в JSON, но возможности закомментировать часть файла очень не хватало
Если вопрос только в этом, почему бы не сделать простейший препроцессор в пару строк bash или Perl, чтобы отфильтровывал закомментированные условным символом строки?
Понятно, что комментировать нужно будет с умом, чтобы сохранить валидный JSON без отфильтрованных строк, но для внутреннего употребления это не проблема?
SquareRootOfZero
30.07.2024 15:02+2Если бы уже достаточно много этих конфигов было написано в JSON, то, возможно, так бы и сделал. А так - погуглил, чо там как с поддержкой комментариев, в YAML поддерживаются, да и синтаксис показался погуманнее, чем в JSON. Опять же, синтаксис в редакторах корректно подсвечивается, code folding корректно работает, и вот это вот всё. А "комментировать с умом" - это ж везде надо, хоть в JSON, хоть в YAML, хоть в C++.
slonopotamus
30.07.2024 15:02+1Если вопрос только в этом, почему бы не сделать простейший препроцессор в пару строк bash или Perl, чтобы отфильтровывал закомментированные условным символом строки?
Потому что будет негде взять редактор, который поймёт как это поделие форматировать и подсвечивать синтаксис.
AlexMih
30.07.2024 15:02Для полностью самодельного языка, стало быть, редактор с подсветкой найти проще.
Fancryer Автор
30.07.2024 15:02Если честно, о таком не задумывался, вот моё решение. Допустим, что в ОС, где мы используем Av, знак вопроса не может встретиться в имени директории. Тогда можно создать мапу, в которой он будет ключом для массива файлов, а имена директорий будут ключами к объектам, которые они описывают.
Допустим, структура проекта такая (не нейронка, у самого похожий есть):
example_project ├── config │ └── environments │ ├── development │ │ ├── database.txt │ │ └── settings.txt │ └── production │ ├── database.txt │ └── settings.txt ├── app │ ├── core │ │ ├── controllers │ │ │ └── museum │ │ │ ├── create.txt │ │ │ ├── read.txt │ │ │ ├── update.txt │ │ │ └── delete.txt │ │ ├── models │ │ │ └─── museum │ │ │ ├── schema.txt │ │ │ └── validations.txt │ │ └── services │ │ └── museum │ │ ├── create_service.txt │ │ ├── read_service.txt │ │ ├── update_service.txt │ │ └── delete_service.txt │ └── utils │ └── logging │ ├── file_logger.txt │ └── console_logger.txt ├── assets │ ├── images │ │ └── logos │ │ ├── main_logo.png │ │ └── footer_logo.png │ └── styles │ └── themes │ ├── light.css │ └── dark.css ├── tests │ ├── integration │ │ └── controllers │ │ └── museum_integration_test.txt │ └── unit │ ├── models │ │ └── museum_unit_test.txt │ └── services │ └── museum_service_test.txt ├── main.txt └── .gitignore
Тогда я описал бы её следующим образом:
; map on value place means that there's inner directory at right narrow_project { ; any name allowed in Av, but forbidden as path part ; could be used there ? ["main.txt" ".gitignore"] config/environments { ; array on value place means that there's file list at right development ["database.txt" "settings.txt"] production ["database.txt" "settings.txt"] } app { core { controllers/museum [ "create.txt" "read.txt" "update.txt" "delete.txt" ] models/museum ["schema.txt" "validations.txt"] services/museum [ "create_service.txt" "read_service.txt" "update_service.txt" "delete_service.txt" ] } utils/logging ["file_logger.txt" "console_logger.txt"] } assets { images/logos ["main_logo.png" "footer_logo.png"] styles/themes ["light.css" "dark.css"] } tests { integration/controllers ["museum_integration_test.txt"] unit { models ["museum_unit_test.txt"] services ["museum_service_test.txt"] } } }
То есть:
В директории есть другие директории - использую мапу, где по некоторому ключу - у меня это знак вопроса - лежит массив файлов
В ней есть только файлы - использую массив с файлами.
SquareRootOfZero
30.07.2024 15:02+1В смысле, это идея, как можно сделать, или вы в своей мудрости настолько предусмотрели, что уже так работает?
Я нашёл пример своего ямловского конфига, вот так это выглядело:
# the most basic path prefixes inp: &inp ../data/input/ out: &out ../data/output/ my_files: !paths - *inp - - auxiliary_data/ - - some_file.yaml - another_file.yaml output_dir_prefix: !path &output_dir_prefix - *out - - result_dir/ final_output_dir1: !path - *output_dir_prefix - - subdir1/ final_output_dir2: !path - *output_dir_prefix - - subdir2/ - - subdir3/
После парсинга переменные my_files, final_output_dir1 и final_output_dir2 содержали в себе полные пути:
['../data/input/auxiliary_data/some_file.yaml', '../data/input/auxiliary_data/another_file.yaml'] ../data/output/result_dir/subdir1/ ../data/output/result_dir/subdir2/subdir3/
Мне иногда прям бывает даже странно, что до сих пор ни в каких форматах ничего подобного не предусмотрено - вроде ж, не сказать чтоб редкий случай. Но вот нет - пишите полные пути, в лучшем случае определите алиасы и каждый раз копипастите их в начало пути.
sinelnikof88
30.07.2024 15:02+3Выскажу непопулярное мнение - стандарт он для того и стандарт , чтоб брать и использовать . Один раз ознакомился , и дальше понимаешь как это работает .
История : коллега занимается внедрением по. По всей стране развернуты сотни кластеров. Сервера общаются между собой. Все хорошо пока не происходит сбой. Причем масштабный. Он дампит обмен серверов . А там json не валидный . 2 дня он не мог понять что это за херня , то ли оборудование глючит , то ли пакеты не доходят , то ли хер знает что... Потом выяснилось что этот json не ,json вовсе, а какое то выблеванное подобие.
Какой то мудак, изобрел свой протокол обмена.и уволился. Было это лет 10 назад и до сбоя никто про это не знал.
Вся история к тому что , не едите мозги ни себе ни людям .
Конфигурация - yaml ,.env, и куча другой херни
Обмен данными - json, xml и то же готовых форматов ДОХЕРА!
Не нужно пихать хер в точилку!!!!
Очередной смузихлеб запихает это в проект, не дай бог уйдет в прод. А потом этот понос всей команде выгребать. С дикой болью и седым менеджером проекта. Который пытается обяснить директору какого хера парк серверов лежит, когда контора теряет сотни тысяч каждый час.
SquareRootOfZero
30.07.2024 15:02+2Так проблема была в том, что был изобретён свой протокол обмена? Или в том, что его изобретатель был мудак? Или в том, что изобретение было нигде не задокументировано и поэтому стало проблемой, когда он уволился? Протокол-то что был - "лучше", "хуже", "такой же, но другой", может, фичи какие-то имел дополнительные, которые нужны были?
Так-то мнение не то что популярное, а общепринятое - не надо изобретать велосипед. Существует готовое решение, устраивает? Бери, пользуйся. А если не устраивает? Можно было бы до сих пор сидеть у пещеры и красный цветок кормить, а то и не кормить, если не одобрят старшие обезьяны, скажут, был тут мудак, развёл костёр и пошёл леопардом съелся.
Хотя, конечно, постановки проблемы в посте автора как-то не хватает - с чего вдруг захотелось изобрести новый JSON, чего в старом так сильно не хватало?
Ogoun
30.07.2024 15:02Согласен, есть же формат .ini, идеален для конфигов, читабелен, больше 30 лет формату. Все эти хипстерские json, yml и прочее даже рядом не стоят, но нет же, пихают в проекты, надо показать что все типа по современному.
VMarkelov
30.07.2024 15:02+4Лично мне хватило просто перейти на JSON5 (https://json5.org/). Полностью совместим с JSON, плюс: есть комментарии, запятая после последнего элемента массива не является ошибкой, если имя ключа map слово без пробелов, то кавычки можно не ставить, также есть шестнадцатеричные константы и многострочные сроки.
Хотя тут есть возможность иметь переменные и вычислять что-то на лету. Мне это нравится, но пока не было в этом необходимости.
playermet
30.07.2024 15:02+1Я для таких целей просто беру Lua c отрезанной частью стандартных библиотек. Одновременно и нормальный ЯП со всеми средствами разработки, и вполне приемлемый синтаксис для конфигурации.
funny_falcon
30.07.2024 15:02Примечательно, что один из непосредственных предков Lua был как раз языком конфигурации. И это до сих пор отражено в синтаксисе: вызов функции с одним параметром без скобок родом оттуда.
Tzimie
קשה מדי