Библиотека JSON Link — это высокопроизводительная библиотека C++ JSON без выделения памяти, поддерживающая:
Библиотека поддерживает и другие режимы синтаксического анализа, которые также можно смешивать.
json_array_iterator
или json_array_range
.json_value
, который позволяет выполнять итерацию/ленивый анализ документа.Некоторые другие примечательные особенности:
boost::multiprecision::cpp_int
или GNU BigNum/Rational mpq_t
Библиотека использует лицензию BSL.
Когда структура документа JSON известна, синтаксический анализ выглядит следующим образом:
MyThing thing = daw::json::from_json<MyThing>( json_string );
или для документов массива, где корнем документа является массив, существует вспомогательный метод, упрощающий работу, и его можно проанализировать следующим образом:
std::vector<MyThing> things = daw::json::from_json_array<MyThing>( json_string2 );
Если структура документа JSON неизвестна, можно создать json_value
, которое действует как представление и позволяет выполнять итерацию и анализ по запросу. Ниже приведен пример открытия json_value
из данных JSON:
json_value val = daw::json::json_value( json_string );
Методы from_json
и to_json
позволяют получить доступ к большинству потребностей синтаксического анализа и сериализации.
Анализатор на основе событий (SAX) можно вызвать через daw::json::json_event_parser
. Он принимает два аргумента: документ json и обработчик событий. Обработчик событий может принять участие в событиях, имея любой из следующих членов:
Сопоставление ваших классов с документами JSON выполняется путем специализации признака daw::json::json_data_contract
. Сопоставленный класс не требует повторного сопоставления, если он является членом другого сопоставленного класса. У типажа json_data_contract
есть две части: первая — это псевдоним типа с именем type
, который сопоставляет элементы JSON с конструктором нашего класса. Это позволяет обойти необходимость частного доступа к классу, предполагая, что данные, которые мы будем сериализовать, также потребуются для создания класса. Например:
struct Thing {
int a;
int b;
};
Конструкция Thing
требует 2 целых числа, и если бы у нас был следующий JSON:
{
"a" : 42 ,
"b" : 1234
}
Мы могли бы выполнить сопоставление следующим образом:
namespace daw ::json {
template <>
struct json_data_contract <Thing> {
static constexpr char const a[] = " a " ;
static constexpr char const b[] = " b " ;
using type = json_member_list<
json_number<a, int >,
json_number<b, int >
>;
};
}
Это говорит о том, что класс JSON в документе будет иметь как минимум два элемента «a» и «b», которые будут целыми числами. Они будут переданы конструктору Thing
, когда daw::json::from_json<Thing>( json_doc );
вызывается или что другой класс имеет сопоставление членов json_class<MemberName, Thing>
. Вышеупомянутый метод сопоставления имен C++17 работает и в будущих версиях C++. Но в C++20 и более поздних версиях имена могут быть встроенными в сопоставление, например json_number<"a", int>
. Вышеупомянутое — это все, что необходимо для анализа JSON, для сериализации статической функции-члена, необходимой в типаже. Взяв предыдущий пример и расширив его, мы могли бы сериализовать Thing
с помощью:
namespace daw ::json {
template <>
struct json_data_contract <Thing> {
static constexpr char const a[] = " a " ;
static constexpr char const b[] = " b " ;
using type = json_member_list<
json_number<a, int >,
json_number<b, int >
>;
};
static auto to_json_data ( Thing const & v ) {
return std::forward_as_tuple ( v. a , v. b );
}
}
Порядок членов, возвращаемых в виде кортежа, должен соответствовать сопоставлению в type
типа type . Это также позволяет передавать результаты методов доступа, если элементы данных не являются общедоступными. Кроме того, класс Thing
должен быть конструируемым из int, int
. Библиотека поддерживает как обычные конструкторы, так и агрегатную инициализацию ( Thing{ int, int }
и Thing( int, int )
) в C++17.
to_json_data
не обязательно должен возвращать кортеж ссылок на существующие члены объекта, но также может возвращать вычисленные значения. Он не пропускает значения r, поскольку они часто являются временными, и это может привести к удаленной отладке. Библиотека подтвердит это static_assert и предложит включить <daw/daw_tuple_forward.h>
и вызвать daw::forward_nonrvalue_as_tuple( ... )
которые сохраняют временные данные и пересылают другие ссылочные типы. Анализаторы работают, создавая каждый аргумент на месте при вызове конструктора класса. Отдельные анализаторы аргументов можно настроить для определенных обстоятельств данных (например, чисел с плавающей запятой и целых чисел). Затем, используя нашу характеристику типа, определяющую аргументы, необходимые для создания класса C++, и их порядок, мы можем просмотреть каждый член в JSON. Теперь мы создаем значение на основе результата каждого синтаксического анализатора; аналогично T{ parse<0, json_string<"name">>( data ), parse<1, json_number<"age", unsigned>>( data ), parse<json_number<2, "number>>( data )}
Для каждого члена поток данных будет перемещаться вперед, пока мы не найдем элемент, который нам нужно проанализировать, сохраняя интересующие нас местоположения для последующего анализа. Этот процесс позволяет нам анализировать и другие классы как члены через метод. json_class<"member_name", Type>
тип сопоставления. Таким образом, каждый признак сопоставления должен иметь дело только со своими конкретными членами, а не с их деталями.
В безымянных контекстах, таких как корневое значение, элементы массива, некоторые типы ключевых значений и списки вариантов элементов, где имя будет no_name
, можно использовать некоторые собственные типы данных C++ вместо типов сопоставления JSON. Сюда входят целые числа, числа с плавающей запятой, bool, std::string, std::string_view, ассоциативные контейнеры, контейнеры последовательностей, подобные типы с нулевыми/необязательными значениями и ранее сопоставленные классы.
Например, чтобы сопоставить массив строк.
template <>
struct daw ::json::json_data_contract<MyType> {
using type = json_member_list<json_array< " member_name " , std::string>>;
};
Чтобы получить последнюю версию, можно использовать vcpkg, порт называется daw-json-link
find_package ( daw-json-link )
#...
target_link_libraries ( MyTarget daw::daw-json-link )
Библиотека представляет собой только заголовок и может быть клонирована вместе с двумя ее зависимостями с последующим добавлением подпапок include/
каждой из них в путь включения компилятора.
Чтобы использовать daw_json_link в ваших проектах cmake, добавление следующего должно позволить ему использовать его вместе с зависимостями:
include ( FetchContent )
FetchContent_Declare(
daw_json_link
GIT_REPOSITORY https://github.com/beached/daw_json_link
GIT_TAG release
)
FetchContent_MakeAvailable(daw_json_link)
#...
target_link_libraries ( MyTarget daw::daw-json-link )
В системе с bash все аналогично и в других системах, для системы можно установить следующее:
git clone https://github.com/beached/daw_json_link
cd daw_json_link
mkdir build
cd build
cmake ..
cmake --install .
Это позволит установить cmake find_package или использовать его в качестве обычного заголовка, если папка включения префикса установки включена в пути включения компилятора.
Ниже будут собраны и запущены тесты.
git clone https://github.com/beached/daw_json_link
cd daw_json_link
mkdir build
cd build
cmake -DDAW_ENABLE_TESTING=On ..
cmake --build .
ctest .
После сборки отдельные примеры также можно протестировать. city_test_bin
требует путь к файлу JSON городов.
./tests/city_test_bin ../test_data/cities.json
Порядок членов в структурах данных обычно должен соответствовать порядку данных JSON, если это возможно. Анализатор работает быстрее, если ему не нужно возвращаться к значениям. Необязательные значения, отсутствующие в данных JSON, также могут замедлить анализ. Если возможно, отправляйте их как нулевые. Парсер не выделяет. Анализируемые типы данных могут использоваться, и это позволяет использовать собственные распределители или их комбинацию, поскольку их структуры данных будут выполнять распределение. По умолчанию для массивов используется std::vector, и если это нежелательно, вы должны указать тип.
В настоящее время библиотека не удаляет/экранирует имена членов при сериализации. Ожидается, что они будут действительными и неэкранированными. Это может стать необязательным дополнением в будущем, поскольку за него придется платить.
Между C++17 и C++20 есть небольшие различия: C++20 позволяет использовать некоторый код, недоступный в C++17.
namespace daw ::json {
template <>
struct json_data_contract <MyType> {
static constexpr char const member_name[] = " memberName " ;
using type = json_member_list<json_number<member_name>>;
};
}
Обе версии C++ поддерживают этот метод именования членов.
При компиляции в компиляторе C++20, помимо передачи char const *
как в C++17, имена членов могут быть указаны непосредственно как строковые литералы. Поддержка компилятора C++20 все еще находится на ранней стадии, и здесь уже есть драконы. Известны проблемы с g++9.x в режиме C++20, и он тестировался только с g++10/11. Здесь будут драконы
namespace daw ::json {
template <>
struct json_data_contract <MyType> {
using type = json_member_list<json_number< " member_name " >>;
};
}
После того как тип данных сопоставлен с помощью json_data_contract
, библиотека предоставляет методы для анализа JSON для него.
MyClass my_class = from_json<MyClass>( json_str );
В качестве альтернативы, если входным данным доверяют, менее проверенная версия может быть быстрее.
MyClass my_class = from_json<MyClass, options::parse_flags<options::CheckedParseMode::no>>( json_str );
Документы JSON с корнем массива используют функцию from_json_array
для анализа.
std::vector<MyClass> my_data = from_json_array<MyClass>( json_str );
В качестве альтернативы, если входным данным доверяют, менее проверенная версия может быть быстрее.
std::vector<MyClass> my_data = from_json_array<MyClass, std::vector<MyClass>, options::parse_flags<options::CheckedParseMode::no>>( json_str );
json_array_iterator
Если вы хотите работать с данными массива JSON, вы можете получить итератор и использовать алгоритмы std для итерации по массиву в данных JSON, которую можно выполнить с помощью json_array_iterator
using iterator_t = json_array_iterator<MyClass>;
auto pos = std::find( iterator_t ( json_str ), iterator_t ( ), MyClass( ... ) );
В качестве альтернативы, если вводу доверяют, вы можете вызвать менее проверенную версию.
using iterator_t = daw::json::json_array_iterator<MyClass, options::CheckedParseMode::no>;
auto pos = std::find( iterator_t ( json_str ), iterator_t ( ), MyClass( ... ) );
json_value
Для DOM, такого как API, часто используемого для таких вещей, как графический интерфейс, и предоставления кода, когда сопоставления неадекватны, можно использовать json_value
. Это используется в инструменте json_to_cpp.
auto jv = daw::json::json_value( json_doc );
Можно использовать путь JSON для извлечения целого числа
int foo = as< int >( jv[ " path.to.int " ] );
Здесь "path.to.int"
— это путь JSON, который представляет собой детализацию класса JSON, например
{
"path" : {
"to" : {
"int" : 5
}
}
}
Можно также выбрать синтаксис типа массива в пути JSON: "path[5]"
выберет 5-й элемент/член "path"
. Если вы хотите сериализовать в JSON. Синтаксис JSON Path также работает с from_json
, from_json_array
и json_array_iterator
.
to_json
std::string my_json_data = to_json( MyClass{} );
Или сериализуйте массив, коллекцию, диапазон или представление вещей. Для работы с этим типом требуется только std::begin(...)
и std::end(...)
. Это позволяет сериализовать, когда тип не является конструируемой коллекцией объектов.
std::vector<MyClass> arry = ...;
std::string my_json_data = to_json_array( arry );
При разборе ошибок по умолчанию выставляется daw::json::json_exception
, включающая информацию о причине и месте сбоя.
Если исключения отключены, библиотека по умолчанию будет вызывать std::terminate
при ошибке синтаксического анализа.
В то время как обработка ошибок по умолчанию включает в себя установку daw::json::json_exception
при ошибках или вызов std::terminate
если исключения отключены. Это поведение можно изменить, установив указатель функции daw::json::daw_json_error_handler
. Единственное требование — функция не возвращает значение. Пример использования этого находится в error_handling_bench_test.cpp.
Проверка ошибок может быть изменена для каждого анализа. from_json
, from_json_array
, json_value
, json_array_iterator
и т. д. все поддерживают параметры синтаксического анализа. вызовы могут быть снабжены опцией парсера. Доступные параметры описаны в элементе кулинарной книги parser_policies.
daw::json::json_exception
имеет функцию-член std::string_view reason( ) const
аналогичную std::exception
's what( )
но возвращает std::string
с большим количеством контекста, чем what( )
. Если вы хотите отключить исключения в среде, в которой они есть, вы можете определить DAW_JSON_DONT_USE_EXCEPTIONS
чтобы отключить выдачу исключений библиотекой, или установить обработчик; это больше не рекомендуется, поскольку для обработчика можно установить одну из двух галок по умолчанию: daw::json::default_error_handling_throwing
или daw::json::default_error_handling_terminating
.
Этого можно добиться, написав специализацию json_data_contract
в пространстве имен daw::json
. Например:
# include < daw/json/daw_json_link.h >
# include < string >
# include < string_view >
# include < vector >
struct TestClass {
int i = 0 ;
double d = 0.0 ;
bool b = false ;
std::string s{};
std::vector< int > y{};
TestClass ( int Int, double Double, bool Bool, std::string S,
std::vector< int > Y)
: i(Int), d(Double), b(Bool), s(std::move( S ) ), y(std::move( Y )) {}
};
namespace daw ::json {
template <>
struct json_data_contract <TestClass> {
using type =
json_member_list<
json_number< " i " , int >,
json_number< " d " >,
json_bool< " b " >,
json_string< " s " >,
json_array< " y " , int >
>;
};
} // namespace daw::json
int main () {
std::string_view test_001_t_json_data = R"( {
"i":5,
"d":2.2e4,
"b":false,
"s":"hello world",
"y":[1,2,3,4]
} )" ;
std::string_view json_array_data = R"( [{
"i":5,
"d":2.2e4,
"b":false,
"s":"hello world",
"y":[1,2,3,4]
},{
"i":4,
"d":122e4,
"b":true,
"s":"goodbye world",
"y":[4,3,1,4]
}] )" ;
TestClass test_class = daw::json::from_json<TestClass>(test_001_t_json_data);
std::vector<TestClass> arry_of_test_class =
daw::json::from_json_array<TestClass>(test_001_t_json_data);
}
См. в обозревателе компилятора.
Поддерживаются как агрегатные, так и пользовательские конструкторы. В описании представлены значения, необходимые для создания типа и порядка. Указанный порядок — это порядок их размещения в конструкторе. Существуют также точки настройки, позволяющие создать свой тип. Класс вроде:
# include < daw/json/daw_json_link.h >
struct AggClass {
int a{};
double b{};
};
namespace daw ::json {
template <>
struct json_data_contract <AggClass> {
using type = json_member_list<
json_number< " a " , int >,
json_number< " b " >
>;
};
}
Тоже работает. То же, но С++17
# include < daw/json/daw_json_link.h >
struct AggClass {
int a{};
double b{};
};
namespace daw ::json {
template <>
struct json_data_contract <AggClass> {
static inline constexpr char const a[] = " a " ;
static inline constexpr char const b[] = " b " ;
using type = json_member_list<
json_number<a, int >,
json_number<b>
>;
};
}
Описания классов рекурсивны со своими подчленами. Используя предыдущий AggClass
его можно включить в качестве члена другого класса.
// See above for AggClass
struct MyClass {
AggClass other;
std::string_view some_name;
};
namespace daw ::json {
template <>
struct json_data_contract <MyClass> {
using type = json_member_list<
json_class< " other " , AggClass>,
json_string< " id " , std::string_view>
>;
};
}
Вышеупомянутое отображает класс MyClass
, у которого есть другой класс, описанный AggClass. Кроме того, вы можете видеть, что имена членов класса C++ не обязательно должны совпадать с именами сопоставленных имен JSON и что строки могут использовать std::string_view
в качестве типа результата. Это важное повышение производительности, если вы можете гарантировать, что буфер, содержащий файл JSON, будет существовать до тех пор, пока существует класс.
Перебор массивов JSON. Входной итератор daw::json::json_array_iterator<JsonElement>
позволяет выполнять итератор по массиву элементов JSON. Технически это итератор ввода, но его можно хранить и повторно использовать как прямой итератор. Он возвращает не ссылку, а значение.
# include < daw/json/daw_json_link.h >
# include < daw/json/daw_json_iterator.h >
# include < iostream >
struct AggClass {
int a{};
double b{};
};
namespace daw ::json {
template <>
struct json_data_contract <AggClass> {
using type = json_member_list<
json_number< " a " , int >,
json_number< " b " >
>;
};
} // namespace daw::json
int main () {
std::string json_array_data = R"( [
{"a":5,"b":2.2},
{"a":5,"b":3.14},
{"a":5,"b":0.122e44},
{"a":5334,"b":34342.2}
] )" ;
using iterator_t = daw::json::json_array_iterator<AggClass>;
auto pos =
std::find_if (
iterator_t (json_array_data),
iterator_t (),
[](AggClass const &element) {
return element. b > 1000.0 ;
}
);
if (pos == iterator_t ()) {
std::cout << " Not found n " ;
} else {
std::cout << " Found n " ;
}
}
Анализ может начаться с определенного члена или элемента. Можно указать необязательный путь к from_json
, from_json_array
, json_value
, json_array_iterator
и т.п. Формат представляет собой список имен элементов, разделенных точками, и, при необходимости, индекс массива member0.member1
, который аналогичен синтаксическому анализу:
{
"member0" : {
"member1" : {}
}
}
member0[5].member1
, который начнет анализ с «member1» в документе, например:
{
"member0" : [
" a " ,
" b " ,
" c " ,
" d " ,
" e " ,
{
"member1" : " "
}
]
}
или
{
"member0" : {
"a" : " " ,
"b" : " " ,
"c" : " " ,
"d" : " " ,
"e" : " " ,
"f" : {
"member1" : " "
}
}
}
Комментарии поддерживаются, если для них используется политика синтаксического анализа. В настоящее время существует две формы политики комментариев.
//
и комментарии в стиле C /* */
. { // This is a comment
"a" /*this is also a comment*/: "a's value"
}
#
комментариев к строке { # This is a comment
"a" #this is also a comment
: "a's value"
}
Политику комментариев можно установить с помощью PolicyCommentTypes
. См. parser_policies для получения дополнительной информации.
Чтобы включить сериализацию, необходимо создать дополнительную статическую функцию в вашей специализации json_data_contract
под названием to_json_data( Thing const & );
который возвращает кортеж членов. Он обеспечит сопоставление вашего типа с аргументами, указанными в описании класса. Для сериализации в строку JSON вызывается to_json( my_thing );
где my_thing
— это зарегистрированный тип или один из фундаментальных типов, таких как контейнеры, карты, строки, bool и числа. Результатом статического метода to_json_data( Thing const & )
является tuple
, порядок элементов которого соответствует порядку в сопутствующем псевдониме type
json_data_contract
type . Из-за способа использования метода кортежи с элементами rvalue приведут к ошибке использования после уничтожения. Если это произойдет, компилятор выдаст ошибку. Включение <daw/daw_tuple_forward.h>
и метода daw::forward_nonrvalue_as_tuple
вместо этого будет сохранять значения r, а не передавать их по ссылке. Часто это результат вычисления элементов кортежа. Используя приведенный выше пример, давайте добавим метод to_json_data
.
# include < daw/json/daw_json_link.h >
# include < tuple >
struct AggClass {
int a;
double b;
};
namespace daw ::json {
template <>
struct json_data_contract <AggClass> {
using type = json_member_list<
json_number< " a " , int >,
json_number< " b " >
>;
static constexpr auto to_json_data ( AggClass const & value ) {
return std::forward_as_tuple ( value. a , value. b );
}
};
}
// ...
AggData value = // ...;
std::string test_001_t_json_data = to_json( value );
// or
std::vector<AggData> values = // ...;
std::string json_array_data = to_json_array( values );
В качестве альтернативы можно выводить данные в любой тип WritableOutput, по умолчанию это включает в себя FILE*, iostreams, контейнеры символов и указатели символов. В json_data_constract
вашего типа. Или, если это разрешено, можно получить оператор ostream<< для своего типа, который вставляет json в выходной поток, добавив псевдоним типа с именем opt_into_iostreams
псевдоним типа не имеет значения, и включить daw/json/daw_json_iostream.h
. Например
# include < daw/json/daw_json_link.h >
# include < daw/json/daw_json_iostream.h >
# include < tuple >
struct AggClass {
int a{};
double b{};
};
namespace daw ::json {
template <>
struct json_data_contract <AggClass> {
using opt_into_iostreams = void ;
using type = json_member_list<
json_number< " a " , int >,
json_number< " b " >
>;
static inline auto to_json_data ( AggClass const & value ) {
return std::forward_as_tuple ( value. a , value. b );
}
};
}
// ...
AggData value = // ...;
std::cout << value << ' n ' ;
// or
std::vector<AggData> values = // ...;
std::cout << values << ' n ' ;
Рабочий пример можно найти по адресу daw_json_iostream_test.cpp или в проводнике компилятора.
error: pointer to subobject of string literal is not allowed in a template argument
constexpr char const member_name[] = " member_name " ;
// ...
json_link<member_name, Type>
Есть несколько определений, влияющих на работу JSON Link.
DAW_JSON_DONT_USE_EXCEPTIONS
— контролирует, разрешены ли исключения. Если это не так, произойдет std::terminate()
при ошибках. Это происходит автоматически, если исключения отключены (например, -fno-exceptions
).DAW_ALLOW_SSE42
— разрешить экспериментальный режим SSE42, обычно режим constexpr работает быстрее.DAW_JSON_NO_CONST_EXPR
— это можно использовать, чтобы позволить создавать классы без специальных членов перемещения/копирования из данных JSON до C++ 20. Этот режим не работает в константном выражении до C++20, когда этот флаг больше не нужен. Старые компиляторы все еще могут работать, но при тестировании некоторые из них приводили к ошибкам ICE или компиляции из-за ошибок в поддержке C++17. Часто может помочь и отказ от использования constexpr.
json_key_value
.std::multimap<std::string, T>
или std::vector<std::pair<std::string, T>>
все члены сохраняются в порядке. Альтернативно, тип json_value
позволит перебирать члены класса и отложенный анализ правильного. См. раздел «Ключевые значения кулинарной книги», в котором демонстрируются эти методы.