Как стать автором
Обновить

Комментарии 49

Для конфигурационных файлов сложно придумать что-либо лучше YAML'а:
  • Минимум лишних символов (никаких фигурных скобок, запятых и даже кавычек, если они явно не нужны)
  • Неограниченная вложенность дерева (у JSON тоже, а вот седой INI таким похвастать не может)
  • Комментарии как в конце строки, так и занимающие всю строку
  • Многострочные значения
  • Массивы в качестве значений
  • Возможность переиспользовать поддеревья (пример)

Даже официальный сайт отформатирован как YAML: www.yaml.org/

А JSON хорош для передачи данных, или для их хранения, куда человеку лезть не нужно.
Серьезно, это IMHO, но лично мне Yaml кажется настолько человекочитаемым, что его становится сложно читать. Вот уже лет пять, как знаю об этом формате, но до сих пор не понимаю его. А вот JSON, тем более, с комментариями, не вызывает никаких трудностей.

Один мой знакомый достаточно точно выразился. Yaml — для любителей python, а JSON — для тех, кто любит C и жить не может без скобочек.
По-моему, если называть переменные своими именами и класть их в логически правильные контейнеры, то проблем не возникает и без комментариев. На худой конец, если очень хочется, то создайте обычный JS файл и изливайтесь мыслью по древу — это будет намного понятнее читать, чем предложенный автором транслятор (меня, лично, напрягает бесконечное количество разнообразных слэшей).
Порой даже названные своими именами значения, уложенные в правильные контейнеры требуют комментариев.

А что касается js файла, далеко не везде есть возможность исполнять js. Если проект, например, на C++, то гораздо логичнее использовать JSON парсер, поддерживающий комментарии.
Ну, если речь идёт о C++ (в коем я, увы, несведущ), то 3-х минутный поиск в гугле выдал мне вот это решение. Или есть причины, почему оно не подходит? (т.к. на мой взгляд оно выглядит намного лаконичней и читабельней). Или решение автора производительней?
Мой изначальный посыл в том, что Yaml — на любителя, и JSON с комментариями может быть гораздо удобнее.

Собственно на js и так есть транслятор из коробки:
JSON.parse(JSON.minify(text))

Для других языков существуют специальные парсеры (как тот же jsoncpp).

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

Ну а само решение проблемы — красивое, потому что короткое и всего одной регуляркой, но не поддерживаемое. Хотя, если вынести части регулярки в define секцию и дать им символьные имена, можно добиться гораздо большей читаемости и поддерживаемости, но труЪ кулл-хацкеры так не делают, да.
проблема то в том что de-facto куча либ уже использует json для конфигов. И что в нем нет комментариев — неимоверно бесит.
Например бывает в bower — нужно временно отключить какую-то библиотеку. Если ее просто удалить — то уже через пару дней просто забудешь что она вообще когда-то была. А вот если закомментировать — то вряд-ли
> И что в нем нет комментариев — неимоверно бесит.

Гораздо больше бесит что надо удалять последнюю запятую :(
А про запятую, кстати, много пожеланий по этой теме встречалось. И в «Hjson, the Human JSON» реализовано и в «JSON5».
Система контроля версий Вам возможно поможет
Все правильно. Для разных целей — разные инструменты.
Всё хорошо, пока табы в отступах не побьются…
Сами по себе?
Как вы догадались, нет. При редактировании руками в каком-нибудь редакторе, раз уж формат позиционируется и как human-writeable. Мало ли где включен retab…

Хотя с точки зрения парсинга он очень приятен.
Ну, любой более-менее вменяемый редактор умеет автоматически определять способ табуляции или позволяет указать его для определённых типов файлов. В конце концов, даже если какой-то редактор и накосячит с табами, то парсер должен выдать соответствующее сообщение об ошибке.
Редактор Sublime Text, например, использует формат JSON с комментариями в качестве конфигурационных файлов. Мне это видится вполне читабельным и удобным решением.

В одном из своих проектов я тоже использовал JSON с комментариями для человекочитаемых конфигов. «Выкусывал» комментарии с помощью регулярных выражений перед тем как отдавать JSON-парсеру.

Обоснование автором JSON отсутствия комментариев мне не понятно. Отсутствие комментариев из коробки просто раздражает.
В статье упомянут (как «очень хакерский способ»). Минус лишь в том, что доступа из JSON к ним нет. Легко обойти, добавляя в конец ключей "_". И приходим к тому, как jsonComm трансформируют в JSON. :)
Зачем к комментариям нужен доступ из JSON?
Ради академического интереса: у нас есть информация, но нет к ней доступа. Из-за этого не можем конвертировать файлы между форматами (JSON-JS-YAML-XML). Получив доступ — сможем.
У нас есть информация для человека, он может её прочитать, что ещё нужно? Хочется сконвертировать, можно другой парсер написать.
Это и есть подход Крокфорда и хакеров, их устраивает этот формат. Других не устраивает избыточность символов, поэтому хочется сконвертировать. Конвертируется, действительно, другим парсером (.comm2json), но суть не этом, а в том, что «хакерам» парсер вообще не нужен, они считают этот формат полностью достаточным.
Генерировать документацию?
Не всех устраивает моё первое объяснение?

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

Но вместо того, чтобы работать с хакерскими комментариями, гораздо удобнее за стандарт принять обычные. (Удобнее не для «суровых программистов», а для всех.) Через jsonComm.comm2json() вводится процедура перевода в доступность из JSON, которую остаётся написать под нужный язык проекта.
Есть такая вещь, как схема json, дополнительным плюсом она дает возможность не только комментировать, но и валидировать и указывать тип данных. Если установить за правило иметь для каждой отдельной структуры json свою схему с заполненным description, комментарии становятся уже не так критичны. Более того для десятков json с одной и той же структурой, нужна лишь одна схема с описанием, а не дублирование комментариев в каждом json. ИМХО, но это более надежно и просто, чем лишний костыль с регулярным выражением (которое может в какой-то момент оказаться не верным).
Схема, как можно посмотреть аналогию на xml.com — другой инструмент, язык описания грамматики. Если нам нужно строить для JSON грамматику, а затем изъясняться на JSON — это одно. Но если нам нужно описывать всегда разные конфиги или всегда разные форматы данных для хранения в БД, а в них пояснять цели конкретных значений, то нам нужны всего лишь комментарии. Если пойдём по пути описания схем, то, во-первых, это хуже хакерского комментария: если в нём мы пишем коммент рядом со строчкой и дублируем ключ, то там пишем коммент в другом файле (и тоже дублируем ключ). В чём выигрыш?

Комментарии в схеме — это комментарии к грамматике. Если их использовать как комментарии к данным — это подобно стрелянию из пушки по воробьям (каждому данному сопоставляем грамматический элемент).

А вообще, если увлечься JSON-грамматиками, то они быстро закончат, как XSLT :).
А так часто нужны комментарии именно к данным? Если вам нужно комментировать данные, то на мой взгляд стоит завести отдельное поле в JSON, где и комментировать данные.

Что проще понять:

{«question»:«42» //The Ultimate Question of Life, the Universe, and Everything}

или

{«question»:{«descripton»:«The Ultimate Question of Life, the Universe, and Everything», «value»:«42»}

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

Плюс такие данные в Json можно обрабатывать штатными средствами.
В конфигах нужны комментарии именно к значениям данных. По Вашему примеру — именно об этом идёт речь, когда я описывал художественное сравнение «суровых программистов» и обычных. Первым достаточно {«question»:{«descripton:», «value»:} И даже {«question»: [value, comment]}. Но все, в том числе и первые, привыкли к {question: value //comment}.
Почему вы считаете, что все привыкли к {question: value //comment}? Мне например более удобно читать правильный JSON c несколькими полями, тем более что IDE хорошо умеет форматировать именно валидный JSON, а руками делать переносы и отступы мучительно долго. А вот такой формат JSON с комментариями наоборот несколько неуютен для привыкших работать с правильным JSON'ом.

Ради интереса попробуем оба формата:

{
   "order1":"N 21", // Накладная на принтер
   "order2":"M 23", // Накладная на мышку
   "order3":"S 231", // Чек за пицу и пиво
}


{
   "order1"  : {
       "number": "N 21", 
       "name": "Накладная на принтер"
   },
   "order2": {
       "number": "M 23", 
       "name": "Накладная на мышку"
   },
   "order3": {
       "number": "S 231", 
       "name": "Чек за пицу и пиво"
   }
}


Не знаю, для меня второй вариант хотя и более длинный, но куда более человекочитаемый и понятный.
Если речь — о данных, то да. Если о комментариях к ним, то первый вариант. Разница в том, что комментарии не планируем использовать (а доступ к ним нужен для конверсии форматов).
Можете привести пример? В случае, который я описал выше поле name для системы тоже не нужно, но его можно ввести для удобства отладки и читаемости json'a. Не вижу проблемы в добавлении поля. которого не планируем использовать в самой системе для повышения читаемости.

Я вижу следующие проблемы у json'a с комментариями:
1) Лишние преобразования, лишний код, лишние возможности для ошибок,
2) Невозможно отправить json c комментариями другим системам и организациям,
3) Скорее всего при отправке с веба на бек, все комментарии исчезнут,
4) Отсутствие комментариев при отладке приложения, так как они удаляются намного раньше,
5) Сложность валидации и форматирование на уровне IDE,
6) Нестандартная форма json'a, требующая привыкания,
7) Требование танцев с бубном при преобразовании форматов,

То есть json с комментариями можно использовать только для локальных конфигов, которые никуда не передаются и используются в рамках одной системы, но при этом нет проблемы комментарии добавлять как отдельное поля (в формате выше).
Ваш пример полностью подходит. Если name для системы не нужно, то слово «name» — излишне, вынос его в значение — тоже. Но главное, что этот спор я предвидел и описал в начале статьи как неизбежный и непримиримый.
1) возможны, но для того отлаживать и тестировать надо;
2) отправить можно, вместе с процедурой очистки, если роботам, без неё, если людям;
3) если напишут на бек-языках чистильщики, то можно пользоваться; Нода уже умеет;
4) как раз для неудаления комментариев задумывались и делались 2 другие функции (comm2json, change);
5) в IDE они распознаются как JS;
6) комментарии — полностью стандартны для JS и Пайтона;
7) это всегда;
> конфигов, которые никуда не передаются
--для этого и задумывалось. Но чтобы не добавлять в отдельные поля — чтобы комментарии для людей, JSON — для скриптов. comm2json — это так, задел для будущего, вклад в конверторы, показ того, что это в рамках подхода — просто и коротко.
Есть в посте, автору не нравится.
Что у этого сайта с цветами O_O

image
Остроумный, пока не наткнёшься на парсер, который будет брать первое из повторяющихся значений.
В случае поиска одного ключа в конфиге обычно нет смысла сканировать конфиг дальше, если уже нашёл значение.
В вашем случае это будет комментарий.
А вы попробуйте в уме оба парсера «написать», сразу поймёте, что это поведение надо программировать специально. Случайно оно получиться не может. Тогда как поведение «берём последнее» получается естественным образом.
Может, если лексер будет обрабатывать значения в словаре справа на лево, что в случае с json — вполне допустимое поведение:

yacc/bison:
dictionary: LBRACE dictionary_list RBRACE
    {
      $$ = $2;
    }
    | LBRACE RBRACE
    { return new Dictionary(); }

dictionary_list: dictionary_element
    {
      $$ = new Dictionary();
      $$->Set($1->key, $1->value);
      delete $1;
    }
    | dictionary_element COMMA dictionary_list
    {
      $$ = $3;
      $$->Set($1->key; $1->value);
      delete $1;
    }
    ;

dictionary_element: string COLON value
    {
       $$ = new DictionaryElement($1, $3);
    }
    ;

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

Сравните разницу в фрагменте:
Обратный порядок:
    | dictionary_element COMMA dictionary_list
    {
      $$ = $3;
      $$->Set($1->key, $1->value);
      delete $1;
    }
    ;


Прямой порядок:
    | dictionary_list COMMA dictionary_element
    {
      $$ = $1;
      $$->Set($3->key, $3->value);
      delete $3;
    }
    ;


Если не брать во внимание возможность того, что ключи в словаре могут повторяться (а с чего бы собственно это предусматривать?), то как именно писать — без разницы и скорее дело привычки того, кто пишет грамматику для лексического анализатора.
Ещё есть такой human json, как HOCON от typesafe. Там возможностей всяких вообще очень много.
Из основных:
— подстановки (причем более продвинутые, чем в yaml);
— автоматические слияния объектов и файлов;
— чтение переменных окружения, если нужно;
— ну и комментарии конечно.
Правда не знаю, как там с реализацией для языков, отличных от java.
github.com/typesafehub/config/
Идея хорошая. Я даже был написал порт его под ноду. Но при использовании оказалось, что некоторые вещи, как необязательные кавычки часто приводят к тому, что очепятка находится пожже чем хотелось бы. Перешел на надстройку над uglify.js (чтобы получать строку и позицию ошибки).
НЛО прилетело и опубликовало эту надпись здесь
Мы вот в своих nodejs проектах используем yamlify — он, в частности, позволяет рекваирить напрямую yaml-файлы вместо json:

var mod = require('mod'); // реквайрим mod.yaml

Причем это работает даже c browserify.

Так же нодовцам советую взгянуть на yapm — отличный поддерживаемый форк npm, позволяющий вместо package.json использовать package.yaml
Мы в Badoo для конфигурационных файлов демонов используем как раз json. Похачили сишную json либу и теперь можем делать комментарии и оставлять последнюю запятую в списках.
Комментарии нужно вводить в стандарт JSON, а не заниматься велосипедостроением:) Если либа с открытыми исходниками — в ее можно добавить поддержку комментариев. Если достаточное количество библиотек поддержит комментарии — появится фактически новый стандарт, расширяющий json. Рано или поздно его стандартизируют официально. И со временем старый — без комментариев — уйдет в небытие

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


  • ("(?:\\[\s\S]|[^"])*") что означает- внутри кавычек есть: что-то экранированное, или что угодно, кроме кавычки
  • ((?:\/\/|#)[^\n]*) что означает — однострочный комментарий
  • (\/\*[\s\S]*?\*\/) что означает многострочный комментарий

Результирующее выражение:


("(?:\\[\s\S]|[^"])*")|((?:\/\/|#)[^\n]*)|(\/\*[\s\S]*?\*\/)

и оставить от него только $1 в строке замены.
https://regex101.com/r/wD0gQ6/1
Почему это будет работать вообще и работать корректно? Движок регексов будет последовательно применять эти альтернативы к каждой позиции текста, встретив кавычку применит первую альтернативу (если JSON валиден), встретив начало комментариев применит другие альтернативы и при этом захватит всю эту составную часть целиком, то есть внутри комментария или кавычек может быть что угодно- ложного срабатывания не будет.


С заменой значения по ключу тоже все относительно просто при таком подходе, но сначала надо изменить регулярное выражение:


  1. Помимо чего-то внутри кавычек в тексте могут быть числа, true, false, null (массивы и объекты не рассматриваю, потому что в топике они тоже не учитываются)
  2. Именованный ключ нам может встретиться после символов { и , — необходимо установить флаг, что когда встретится что-то в кавычках — это будет ключ.

Результирующее регулярное выражение:


([,{])|("(?:\\[\s\S]|[^"])*"|true|false|null|[0-9.eE+-]+)|((?:\/\/|#)[^\n]*)|(\/\*[\s\S]*?\*\/)

Весь код помещается в несколько строк:


var regex = /([,{])|("(?:\\[\s\S]|[^"])*"|true|false|null|[0-9.eE+-]+)|((?:\/\/|#)[^\n]*)|(\/\*[\s\S]*?\*\/)/g;
var nextIsKey = false;
var nextIsReplaced = false;
var replaceKey = "aaa";
var replaceValue = '"Its works!"'; // заменяем строковым значением
var replacer = function( $0, $1, $2, $3, $4 ) {
    if ( typeof $1 !== 'undefined' ) {
        nextIsKey = true;
    } else if ( typeof $2 !== 'undefined' ){
        if ( nextIsReplaced ) {
            nextIsKey = false;
            nextIsReplaced = false;
            return replaceValue;
        };
        if ( nextIsKey && $2 == '"'+replaceKey+'"' ) nextIsReplaced = true;
        nextIsKey = false;
    }
    return $0; // не меняем ничего, если это не указано отдельно
};
console.log( jComm.replace( regex, replacer ) );

https://jsfiddle.net/hb5LzuL7/


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


P.S. Не сочтите меня снобом, пожалуйста. Просто начал писать комментарий про неоправданно сложное регулярное выражение там где все решается значительно проще, но заодно накидал код замены, поэтому такой большой комментарий вышел.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории