Pull to refresh

Comments 51

Без какой-либо рефлексии это всё не так интересно. Хотя всё же безусловно интересно.
Пока мест можно и на темплейтах и pointer-to-member. Вполне себе сериализуется нормально.

А вот мануальный парсинг — это ад и студенчество.
А что есть «на темплейтах и pointer-to-member» — это про рефлексию? Давно на плюсах не писал, сейчас вот снова вернулся с дотнета.
А так в принципе какой-нить интерфейс ISerializable с методами сериализации и десериализации, да на списках инициализации — вполне наглядно и читаемо. С автоматической сериализацией через рефлексию всё равно всегда нужно мутить костыли по преобразованию типов.
Это про замену рефлексии.

struct ModelA {
    std::string id;
    int         value;

    static const JSON::parse_map parse_map;
};
const JSON::parse_map ModelA::parse_map = boost::assign::list_of
    (JSON::map("id",    &ModelA::id))
    (JSON::map("value", &ModelA::value));


Ну и т.д. — вообще все можно со строгой типизацией сделать.
Неплохо, а вот часто при сериализации надо типы конвертить, boost::assign::list_of сможет такое?
Например у нас в json пишется массив строк, а когда читается то создаётся массив объектов, каждый из которых читается из других файлов, где каждая строка — это имя файла.
В том примере JSON::map есть template function которая возвращяет пару строка/boost::function для вызова парсера в конкретный члена класса (через pointer-to-member). Парсер тоже темплейт по типу.

То есть к примеру не проблема, чтобы было чтото типа
struct ModelB {
    std::string id;
    FileBuffer string;
};


В вашем примере — map содержимого файла с именем из JSON в строку — придется немного подправить и явно указать метод для парсинга. По типу:
const JSON::parse_map ModelC::parse_map = boost::assign::list_of
    (JSON::map("id",    &ModelC::id))
    (JSON::map("text", &ModelC::text, &my_custom_parser));

bool my_custom_parser(std::string& value, const char*const buffer, const size_t size) {
    // Кодить тут
}


Это все не гипотетически а буквально в 250 строк включая каменты работает поверх JSMN.
А почему только JSON. Что за ограничения! Хочу SOAP в C++…

Странная статья. Ну есть библиотека, ну и что.
Конечно спасибо за ссылки. Кому ни будь обязательно пригодится.

Но это, с моей стороны, был сарказм на тему «о вхождение в стандарт языка».
Только, насколько я понимаю,
// поиск элемента
if (o.find("foo") != o.end()) {
  // не найдено
}

Внутри проверки не “не найдено”, а как раз найдено.
// добавляем массив строк (будет храниться как std::vector<std::string>)
// обратите внимание — используются списки инициализации
j["companies"] = { "Infopulse", "TM" };

// добавляем ещё один объект — на этот раз используем список инилиализации с парами «ключ»-«значение»
j["user"] = { {"name", "tangro"}, {"active", true} };
Для пар ключ-значение и массива используется одинаковые синтаксис?
Т.е. массив массивов уже не определить?
Например такой JSON:
[
    ["name", "tangro"],
    ["active", true]
]
Определить, но выглядеть будет чуть менее наглядно:
json array_not_object = { json::array({"name", "tangro"}), json::array({"active", true}) };
Неплохо дело обстоит и в языках, где JSON не входит в сам язык, но поддерживается стандартной библиотекой (Python, Ruby): импортируешь модуль — и готово.

Во-первых, тянуть в проект огромный фреймворк ради одного JSON — как-то уныло. Ну ладно, допустим фреймворк у вас был и так. Но тогда придётся писать работу с JSON в терминах фреймворка, а это, как правило, тихий ужас.

В Python и Ruby именно так и поступили, «унылый огромный фреймворк» включили прямо в стандартную библиотеку и тем самым не оставили выбора разработчику. В C++ есть выбор — подключить фреймворк (аналогично модулю) и тогда ситуация полностью аналогична Python/Ruby либо, если надо, не подключать фреймворк.
Тонкость в том, что «фреймворк» подразумевает группу библиотек, имеющих как правило некую общую часть. То есть из фреймворка невозможно вытащить одну библиотеку с гарантией того, что она не потянет за собой что-то еще.
А вот если бы эта «общая часть» была в стандарте языка, то вместо «фреймворков» были бы «наборы библиотек» — уже никак не связанных между собой.
Вывод — в ядро языка нужно добавлять все то, что потенциально может быть «связующим кодом» между различными библиотеками. В том же boost к этой группе относятся как библиотеки, явно отмеченные «language feature emulation», так и некоторые другие — но концептуально являющиеся скорее языковыми фичами, чем сторонними библиотеками (функциональное программирование, сигналы и слоты, некоторые вещи из метапрограммирования, сопрограммы. рефлексия (которой еще нет) и т.д.). А библиотека — это то, что решает не общеязыковую задачу, а прикладную — например, сетевой протокол, формат данных (тот же json), прикладная математика, криптография и т.п.
библиотека — это то, что решает не общеязыковую задачу, а прикладную — например, сетевой протокол, формат данных (тот же json), прикладная математика, криптография и т.п.

Тут вы говорите, что Standard Template Library никакой библиотекой на самом деле не является — несколько необычное определение.
Вывод — в ядро языка нужно добавлять все то, что потенциально может быть «связующим кодом» между различными библиотеками.

Прикладной/связующий — это понятия относительные, отсюда и многоуровневая иерархия зависимостей между библиотеками. Невозможно разделить весь код на всего лишь два уровня — базовый и прикладной. «Потенциально» связующим может быть код любого уровня, как только появится необходимость написать нечто ещё более прикладное. Например, из-за того, что в Python интегрирован JSON, «прикладные» библиотеки для Python полагаются на стандартную реализацию JSON, хотя по вашему JSON должен быть вынесен из ядра языка. В то же время JSON для многих библиотек является «общей частью».
Тут вы говорите, что Standard Template Library никакой библиотекой на самом деле не является — несколько необычное определение.

Формально она является частью языка, а не библиотекой (и, кстати, давно уже называется не «STL» а просто «стандартаная библиотека»). То есть разработчики компиляторов вообще могут включить весь код стандартной библиотеки внутрь компилятора, и даже заголовочные файлы типа vector не будут открываться как файлы, а будут чем-то вроде ключевых слов. Хотя конечно это странно (и для меня тоже), но что поделаешь — С++ сам по себе старый язык, а еще наследие Си тащить надо…
Собственно, библиотека уже сильно переплетена с компиляторами. Например, для std::initializer_list на самом деле жестко прошито, как такое компилировать. Если вы скопируете стандартный код заголовочного файла initializer_list и просто переименуете класс (скажем в std::initializer_list2) то ничего работать не будет, несмотря на полную идентичность остального кода.

Что касается json — то я бы сказал, что общей частью должна быть концепция иерархического представления данных, а не конкретно json (есть еще и xml, и yaml, и куча других аналогичных средств). В С++ с этим тоже не очень, даже массивы не являются полностью first-class objects.
А json — это как раз библиотека. Конечно, крайне желательно, чтобы в официальной библиотеке языка была стандартная реализация «из коробки» чтения и записи файлов json (равно как и xml). Но у программиста всегда должна быть возможность написать свою реализацию и подключить ее к языковой абстракции «иерархическое представление данных».
Был слегка удивлён после прочтения. Я питал иллюзию, что JSON идёт «с коробки» в любом языке. Даже в небогоугодном PHP (на котором я пишу), начиная с 5.2 функции работы с ним являются частью ядра.

Спасибо за статью.
Плюсы слишком уж общие, чтобы тянуть такое в ядро. Да и есть проблемы посерьезнее, например, стандартными плюсами даже директории не обойдешь в файловой системе.
Вообще, есть boost, который по факту расширение стандартной библиотеки.
Знаете в чем парадокс? Из всего богатства синтаксиса, проиллюстрированного вашими примерами (отличными, кстати), в реальных проектах будет использоваться только классический стиль (STL-стиль). Почему? Да потому, что роль JSON в C++ проектах — сериализация/десериализация чего-либо в строку. А конкретный формат при нормальном дизайне отделяется от всего остального кода слоем абстракции. То есть создаваться и заполняться будут не JSON-объекты, а обычные C++ объекты. А всей остальной красотени просто не найдется места. То есть найдется, но не в работе с JSON.
Ну а что вы хотели, если автора удивляет тот факт, что парсер json находится в разделе работы с деревьями? Boost делают очень не глупые люди, которые умеют отделять общее (древовидную структуру данных), от частного представления — например json.
Когда я беру слово, оно означает то, что я хочу, не больше и не меньше© Шалтай-болтай.
Парсер есть, но не всегда он удобен. Разработчики Boost.PropertyTree пошли на допущение:
The property tree dataset is not typed, and does not support arrays as such.

Кстати да, очень неудобно что при записи json из boost.ptree мы на выходе получаем все значения только как строки.
Property tree поддерживает json плохо. В частности, плохо поддерживаются массивы (например, массив не может быть «корневым» объектом), значения свойств всегда сохраняются в виде строки. Долго компилируется даже на простых примерах.
А мне кажется списки инициализации и rang-based for и там могут найти применение. Но в основном вы, конечно, правы.
Я использую jsoncpp вроде бы у них есть какие-то движения в сторону C++11
1. 9 reinterpret_cast'ов в такого рода библиотечке — это даже не C++11, это все C++100500
2. Не уверен, но похоже, что только char нормально поддерживается.
3. Почему codepoint имеет тип size_t?
4. То, что parse принимает стрим или стандартный basic_string, выглядит излишним ограничением. Почему не пара итераторов?
5. Одно из многих возможных движений в направлении hardcore C++ — сделать JSON парсер отдельным темплейтным компонентом, чтобы предложенная схема хранения могла быть заменена на более подходящую пользователю.
кхм, по поводу второго пункта — скорее всего Вы правы. В первоначальном варианте — не было basic_json, был просто json, basic_json предложил уже я и идея, внезапно, принялась.
Исправьте только:
Итак, пусть у нам нужно создать вот такой JSON-объект:… «nothing»: nullptr < — тут должен быть просто null
Формат JSON не предусматривает никаких nullptr, только null.
Ах, да, моя вина. В коде на С++ хотел подчеркнуть использование nullptr и, видимо, скопипастил и в сам объект.
Спасибо.
А как у библиотеки с производительностью? Мы тестировали кучу разных либ для хайлоад-сервера, у которого JSON — основной формат запросов/ответов. И остановились на RapidJSON из-за отличной скорости парсинга строки с json.
> Для линейно-упорядоченных контейнеров порядок элементов будет сохранён, для ассоциативных, понятное дело — нет.

Поправьте меня, если не так. Но для ассоциативных контейнеров set и map порядок следования элементов все же детерминирован и определяется согласно используемому отношению эквивалентности.
Да, Вы правы. Я хотел подчеркнуть, что для JSON-массивов важен порядок ([0,1] и [1,0] — разные массивы) и используя линейно-упорядоченные контейнеры его можно сохранить. Что же касается JSON-объектов, то порядок свойств не важен ({'a': 1, 'b':2} и {'b':2, 'a': 1} — одинаковые объекты) и в общем-то нам всё-равно, как библиотека сериализует ассоциативный контейнер в строку. Но упомянутая в статье библиотека действительно сохраняет порядок для упорядоченных ассоциативных контейнеров (и, понятное дело, не делает этого для unordered-версий). Я поправлю текст, спасибо за уточнение.
Все так, но я видел людей, которые хотели чтобы в ситуации:
{
  "bbb":"foo",
  "aaa":"bar"
}

обход был бы в порядке: bbb, aaa, а не в лексикографическом.
Вообще суть в том, что никто не должен писать программы или строить модели данных, в котором этот порядок был бы важен. Нужен порядок — используйте массив.
К сожалению порядок бывает важен при десериализации, поэтому наверное никто и не прикрутил к Boost.Serialization поддержку Json.
Всё же было бы неплохо чтобы визуально

json j2 = 
    {
      {"name", "Habrahabr"},
      {"nothing", nullptr},
      {"answer", {
        {"everything", 42}
      }},
      {"companies", {"Infopulse", "TM"}},
      {"user", {
        {"name", "tangro"},
        {"active", true}
      }}
    }


Совпадал с полученным файлом.

Кстати а что с форматированием? Возможность вывода с отступами или в одну строку очень полезна.
Посмотрите на использование примера с raw-string — там всё совпадает.

С форматированием тоже всё ок:

// serialization with pretty printing
// pass in the amount of spaces to indent
std::cout << j.dump(4) << std::endl;
// {
//     "happy": true,
//     "pi": 3.141
// }
включаем инклюдом — это как масло масляное :)
Заинклюдим инклюд-файл директивой #include!

Я хотел подчеркнуть, что не требуется ни статическая, ни динамическая линковка.
да понятно, просто глаз зацепился :)
Кстати, хотел бы спросить: почему JavaScript упомянут в списке языков, а тот же PHP — нет?
Ну, не упомянуты ещё сотни языков. Что же — все перечислять?
Зачем же? Достаточно указать только самые популярные. Все-таки PHP здесь оставляет далеко позади Ruby и даже Python, и являлся бы отличным языком для веб-разработки, если бы школотень не клепала бы на нем себе сайтики для игровых кланов, таким образом все больше портя ему репутацию…
Выглядит достаточно бодро, что бы попробовать заюзать в новом поделии, спасибо.
Sign up to leave a comment.