Здравствуйте, судя по моему скромному опыту, с таким форматом обменом данными, как JSON, знакомы очень многие. Сейчас он является де‑факто стандартом в веб‑приложениях (при обмене данными между клиентом и сервером), а некоторые производные формы JSON используются и в других местах — например, BSON в MongoDB или MessagePack в Pinterest или Redis (поддерживается в скриптинге). Я тоже хорошо знаком с JSON и довольно длительное время регулярно его использовал, однако...

Однажды мне в голову пришла мысль: «А почему бы не попробовать сократить JSON, написав свой формат и оставив его при этом совместимым со своим родителем?». Сказано — сделано, благо что в тот момент я писал в качестве пет‑проекта маленький движок для текстовых квестов, конфиги и темы к которому писались именно на JSON, то есть пространство для экспериментов было. Так как я уже был знаком с тем, как писать формальные грамматики для ANTLR, я быстро набросал её концепт, взяв за основу грамматику JSON и добавив сахара по вкусу (до зубного скрежета). Так как мой движок уже назывался Lirot (с иврита — «видеть»), я не стал заморачиваться и назвал новый язык для его конфигурационных Av — это пятый месяц еврейского календаря, а ещё он примерно соответствует июлю — началу августа. Это было как раз кстати, ведь Av я начал писать именно в июле.

Что из нового я добавил в Av:

  1. Комментарии — есть только однострочные, начинаются на точку с запятой и идут до конца строки;

  2. Идентификаторы (Id) — можно использовать вместо строк там, где ожидается ключ или значение. Представляют собой просто целые слова;

  3. Шестнадцатеричные целые числа (HexInt) — беззнаковые, иногда удобно писать сразу их, а не строки. Например, я использовал их в конфигах тем для движка;

  4. Байтовые последовательности (bytes), которые можно составлять из HexInt с любым количеством знаков (желательно разумным). Концепт подразумевает, что выравнивание и разбивка последовательностей на целые числа определённой разрядности лежит на плечах реализующего, в моей реализации на Java они парсятся как Byte);

  5. Биндинги на уровне массивов и мап (в Av так называются объекты) — полезная штука для сокращения повторяемых значений;

  6. Ссылки на существующие константы или на их свойства;

  7. Чёткое разделение number на int и float. Int представлен десятичными и шестнадцатеричными литералами;

  8. Отдельный тип bool, к которому принадлежат true и false;

  9. Nil — прямой аналог null, который в некоторых случаях помогает сократить объём текста, а также просто нравится мне больше;

  10. Общая категория atom, куда входят int, float, string, bool, nil и id;

  11. Дополнительные виды ключей в мапах — можно использовать не только строки, но и любые атомы;

  12. Опциональные запятые в конце массивов и мап.

  13. Интерполяция строк;

  14. Опциональные фигурные скобки в топ‑левеле;

  15. Семь видов заимствований (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. Первичный марафет

На первом шаге я:

  1. Заменяю строки идентификаторами там, где это возможно;

  2. Убираю двоеточия после ключей;

  3. Убираю запятые;

  4. Опускаю фигурные скобки в топ‑левеле.

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)


  1. Tzimie
    30.07.2024 15:02

    קשה מדי


  1. kuza2000
    30.07.2024 15:02

    А где опенсорс либа?)


    1. Fancryer Автор
      30.07.2024 15:02
      +1

      Пока есть только локальная версия, планирую через пару недель выложить на Гитхаб, когда реализация будет чуть менее сырой.


  1. ruomserg
    30.07.2024 15:02
    +6

    О! Еще одно подтверждение - любой достаточно продвинутый язык конфигурации стремится стать эквивалентом машине тьюринга, и превратиться в язык программирования без адекватных инструментальных средств, проверки синтаксиса, отладчика, но с комьюнити из целых нескольких человек... :-)


  1. IIvana
    30.07.2024 15:02

    ; object3_unite 'object1 + 'object2
    object3_unite {
    	id 2
    	name "Object One"
    	color red
    
    • поручик, а почему не объединились числа, строки и идентификаторы?

    • раскладец, батенька, раскладец! (С)


  1. delphinpro
    30.07.2024 15:02
    +2

    К чему хотелось бы придраться =)

    Убрал двоеточие и запятые - если опционально то ок. Иначе зря.
    Про запятые говорю те, которые пишутся в столбик (как в sass необязательные ";" в конце свойств). Допустимая запятая после последнего ключа - ок.

    кстати, если убрать еще и фигурные скобки, то получится почти yaml =)

    Ну и главное - для удобного использования нужны плагины для популярных IDE, f их нет, насколько я понимаю, раз уж сам парсер даже в открытом доступе не присутствует.

    ну а так в целом - интересно. на плюсик тянет =)


    1. Fancryer Автор
      30.07.2024 15:02

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


      1. delphinpro
        30.07.2024 15:02

        Если говорить об альтернативных форматах json, то лично для меня основной стоп-фактор их использования - отсутствие поддержки в IDE. Иногда хотелось бы использовать в качестве конфига и при этом не заморачиваться с лишними кавычками (в ключах) и добавлять комментарии. Пакетов полно. Но открываешь такой файл в редакторе и он весь красный. Думаешь - да в *опу, заюзаю конфиг в виде php массива.


  1. Anarchist
    30.07.2024 15:02

    HOCON уже видели? :)


    1. 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 ] ]


      1. Anarchist
        30.07.2024 15:02

        Любой JSON - валидный HOCON, так что пробелы и переносы строк там важны только в случаях, когда используется только сокращенный синтаксис без кавычек. Но в HOCON ещё и другие интересные фишки: включения документов, reference.conf, ccылки на иные параметры. Ваша попытка создать шестнадцатый стандарт интересна, конечно, но пока укладывается в картинку https://imgs.xkcd.com/comics/standards.png

        Но серьезно, достойно уважения. Я бы присмотрелся скорее к YAML и сделал что-то похожее на него в духе переделки JSON в HOCON. В конце концов для конфигов более всего важна человекочитабельность.


  1. SquareRootOfZero
    30.07.2024 15:02

    Пару лет назад писал программу, использовавшую очень большие и развесистые конфигурационные файлы - сперва эти файлы были в JSON, но возможности закомментировать часть файла очень не хватало, переделал на YAML, тем более его как-то в целом удобнее было руками редактировать. Через какое-то время стало не хватать возможности, не повторяясь, описывать структуру вложенных директорий, вместо того, чтобы к каждому файлу данных прописывать полный путь (YAML позволяет расширять парсер, дописал сам), как-то типа так:

    - myfolder/
      -- somefile.abc
      -- anotherfolder/
      -- -- anotherfile.xyz

    Не помню уже синтаксис ни своей писанины, ни даже самого формата YAML, но идея, думаю, понятна. Ваш формат не умеет что-то такое "из коробки"?


    1. AlexMih
      30.07.2024 15:02

      в JSON, но возможности закомментировать часть файла очень не хватало

      Если вопрос только в этом, почему бы не сделать простейший препроцессор в пару строк bash или Perl, чтобы отфильтровывал закомментированные условным символом строки?

      Понятно, что комментировать нужно будет с умом, чтобы сохранить валидный JSON без отфильтрованных строк, но для внутреннего употребления это не проблема?


      1. SquareRootOfZero
        30.07.2024 15:02
        +2

        Если бы уже достаточно много этих конфигов было написано в JSON, то, возможно, так бы и сделал. А так - погуглил, чо там как с поддержкой комментариев, в YAML поддерживаются, да и синтаксис показался погуманнее, чем в JSON. Опять же, синтаксис в редакторах корректно подсвечивается, code folding корректно работает, и вот это вот всё. А "комментировать с умом" - это ж везде надо, хоть в JSON, хоть в YAML, хоть в C++.


      1. slonopotamus
        30.07.2024 15:02
        +1

        Если вопрос только в этом, почему бы не сделать простейший препроцессор в пару строк bash или Perl, чтобы отфильтровывал закомментированные условным символом строки?

        Потому что будет негде взять редактор, который поймёт как это поделие форматировать и подсвечивать синтаксис.


        1. AlexMih
          30.07.2024 15:02

          Для полностью самодельного языка, стало быть, редактор с подсветкой найти проще.


          1. slonopotamus
            30.07.2024 15:02

            Нет, поэтому полностью самодельный язык ещё хуже :)


    1. 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"]
          }
        }
      }

      То есть:

      • В директории есть другие директории - использую мапу, где по некоторому ключу - у меня это знак вопроса - лежит массив файлов

      • В ней есть только файлы - использую массив с файлами.


      1. 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/

        Мне иногда прям бывает даже странно, что до сих пор ни в каких форматах ничего подобного не предусмотрено - вроде ж, не сказать чтоб редкий случай. Но вот нет - пишите полные пути, в лучшем случае определите алиасы и каждый раз копипастите их в начало пути.


        1. Fancryer Автор
          30.07.2024 15:02

          Просто идея, да.


  1. sinelnikof88
    30.07.2024 15:02
    +3

    Выскажу непопулярное мнение - стандарт он для того и стандарт , чтоб брать и использовать . Один раз ознакомился , и дальше понимаешь как это работает .

    История : коллега занимается внедрением по. По всей стране развернуты сотни кластеров. Сервера общаются между собой. Все хорошо пока не происходит сбой. Причем масштабный. Он дампит обмен серверов . А там json не валидный . 2 дня он не мог понять что это за херня , то ли оборудование глючит , то ли пакеты не доходят , то ли хер знает что... Потом выяснилось что этот json не ,json вовсе, а какое то выблеванное подобие.

    Какой то мудак, изобрел свой протокол обмена.и уволился. Было это лет 10 назад и до сбоя никто про это не знал.

    Вся история к тому что , не едите мозги ни себе ни людям .

    Конфигурация - yaml ,.env, и куча другой херни

    Обмен данными - json, xml и то же готовых форматов ДОХЕРА!

    Не нужно пихать хер в точилку!!!!

    Очередной смузихлеб запихает это в проект, не дай бог уйдет в прод. А потом этот понос всей команде выгребать. С дикой болью и седым менеджером проекта. Который пытается обяснить директору какого хера парк серверов лежит, когда контора теряет сотни тысяч каждый час.


    1. SquareRootOfZero
      30.07.2024 15:02
      +2

      Так проблема была в том, что был изобретён свой протокол обмена? Или в том, что его изобретатель был мудак? Или в том, что изобретение было нигде не задокументировано и поэтому стало проблемой, когда он уволился? Протокол-то что был - "лучше", "хуже", "такой же, но другой", может, фичи какие-то имел дополнительные, которые нужны были?

      Так-то мнение не то что популярное, а общепринятое - не надо изобретать велосипед. Существует готовое решение, устраивает? Бери, пользуйся. А если не устраивает? Можно было бы до сих пор сидеть у пещеры и красный цветок кормить, а то и не кормить, если не одобрят старшие обезьяны, скажут, был тут мудак, развёл костёр и пошёл леопардом съелся.

      Хотя, конечно, постановки проблемы в посте автора как-то не хватает - с чего вдруг захотелось изобрести новый JSON, чего в старом так сильно не хватало?


    1. Ogoun
      30.07.2024 15:02

      Согласен, есть же формат .ini, идеален для конфигов, читабелен, больше 30 лет формату. Все эти хипстерские json, yml и прочее даже рядом не стоят, но нет же, пихают в проекты, надо показать что все типа по современному.


  1. VMarkelov
    30.07.2024 15:02
    +4

    Лично мне хватило просто перейти на JSON5 (https://json5.org/). Полностью совместим с JSON, плюс: есть комментарии, запятая после последнего элемента массива не является ошибкой, если имя ключа map слово без пробелов, то кавычки можно не ставить, также есть шестнадцатеричные константы и многострочные сроки.

    Хотя тут есть возможность иметь переменные и вычислять что-то на лету. Мне это нравится, но пока не было в этом необходимости.


  1. playermet
    30.07.2024 15:02
    +1

    Я для таких целей просто беру Lua c отрезанной частью стандартных библиотек. Одновременно и нормальный ЯП со всеми средствами разработки, и вполне приемлемый синтаксис для конфигурации.


    1. funny_falcon
      30.07.2024 15:02

      Примечательно, что один из непосредственных предков Lua был как раз языком конфигурации. И это до сих пор отражено в синтаксисе: вызов функции с одним параметром без скобок родом оттуда.