json
из литералов JSONСуществует множество библиотек JSON, и у каждой может быть своя причина для существования. У нашего класса были следующие цели проектирования:
Интуитивный синтаксис . В таких языках, как Python, JSON воспринимается как первоклассный тип данных. Мы использовали всю магию операторов современного C++, чтобы добиться того же ощущения в вашем коде. Посмотрите примеры ниже, и вы поймете, что я имею в виду.
Тривиальная интеграция . Весь наш код состоит из одного заголовочного файла json.hpp
. Вот и все. Никакой библиотеки, никакого подпроекта, никаких зависимостей, никакой сложной системы сборки. Класс написан на ванильном C++11. В общем, все не должно требовать корректировки флагов компилятора или настроек проекта.
Серьезное тестирование . Наш код тщательно протестирован и покрывает 100% кода, включая все исключительные ситуации. Кроме того, мы проверили с помощью Valgrind и Clang Sanitizers отсутствие утечек памяти. Google OSS-Fuzz дополнительно проводит нечеткие тесты для всех парсеров 24 часа в сутки, 7 дней в неделю, эффективно выполняя на данный момент миллиарды тестов. Для поддержания высокого качества проект следует передовому опыту Core Infrastructure Initiative (CII).
Остальные аспекты были для нас не столь важны:
Эффективность памяти . Каждый объект JSON имеет накладные расходы в размере одного указателя (максимальный размер объединения) и одного элемента перечисления (1 байт). Обобщение по умолчанию использует следующие типы данных C++: std::string
для строк, int64_t
, uint64_t
или double
для чисел, std::map
для объектов, std::vector
для массивов и bool
для логических значений. Однако вы можете создать шаблон обобщенного класса basic_json
в соответствии со своими потребностями.
Скорость . Конечно, существуют более быстрые библиотеки JSON. Однако, если ваша цель — ускорить разработку, добавив поддержку JSON с одним заголовком, то эта библиотека — то, что вам нужно. Если вы знаете, как использовать std::vector
или std::map
, значит, все у вас уже настроено.
Дополнительную информацию см. в правилах внесения взносов.
Вы можете спонсировать эту библиотеку на сайте GitHub Sponsors.
Спасибо всем!
❓ Если у вас есть вопрос , проверьте, есть ли на него ответ в разделе «Часто задаваемые вопросы» или в разделе «Вопросы и ответы» . Если нет, задайте там новый вопрос .
Если вы хотите узнать больше о том, как использовать библиотеку, ознакомьтесь с остальной частью README , просмотрите примеры кода или просмотрите страницы справки .
? Если вы хотите лучше понять API , ознакомьтесь с Справочником по API .
? Если вы обнаружили ошибку , пожалуйста, проверьте FAQ , если это известная проблема или результат дизайнерского решения. Пожалуйста, также просмотрите список задач , прежде чем создавать новую задачу . Предоставьте как можно больше информации, чтобы помочь нам понять и воспроизвести вашу проблему.
Существует также набор документации для браузеров документации Dash, Velocity и Zeal, который содержит полную документацию в виде автономного ресурса.
Вот несколько примеров, которые дадут вам представление о том, как использовать этот класс.
Помимо приведенных ниже примеров, вы можете:
→ Проверьте документацию
→ Просмотрите отдельные файлы примеров
Каждая функция API (описанная в документации API) имеет соответствующий отдельный файл примера. Например, функция emplace()
имеет соответствующий файл примера emplace.cpp.
Класс json
предоставляет API для управления значением JSON. Чтобы создать объект json
путем чтения файла JSON:
# include < fstream >
# include < nlohmann/json.hpp >
using json = nlohmann::json;
// ...
std::ifstream f ( " example.json " );
json data = json::parse(f);
json
из литералов JSON Предположим, вы хотите жестко запрограммировать это буквальное значение JSON в файле как объект json
:
{
"pi" : 3.141 ,
"happy" : true
}
Существуют различные варианты:
// Using (raw) string literals and json::parse
json ex1 = json::parse( R"(
{
"pi": 3.141,
"happy": true
}
)" );
// Using user-defined (raw) string literals
using namespace nlohmann ::literals ;
json ex2 = R"(
{
"pi": 3.141,
"happy": true
}
)" _json;
// Using initializer lists
json ex3 = {
{ " happy " , true },
{ " pi " , 3.141 },
};
Вот несколько примеров, которые дадут вам представление о том, как использовать этот класс.
Предположим, вы хотите создать объект JSON.
{
"pi" : 3.141 ,
"happy" : true ,
"name" : " Niels " ,
"nothing" : null ,
"answer" : {
"everything" : 42
},
"list" : [ 1 , 0 , 2 ],
"object" : {
"currency" : " USD " ,
"value" : 42.99
}
}
С помощью этой библиотеки вы можете написать:
// create an empty structure (null)
json j;
// add a number that is stored as double (note the implicit conversion of j to an object)
j[ " pi " ] = 3.141 ;
// add a Boolean that is stored as bool
j[ " happy " ] = true ;
// add a string that is stored as std::string
j[ " name " ] = " Niels " ;
// add another null object by passing nullptr
j[ " nothing " ] = nullptr ;
// add an object inside the object
j[ " answer " ][ " everything " ] = 42 ;
// add an array that is stored as std::vector (using an initializer list)
j[ " list " ] = { 1 , 0 , 2 };
// add another object (using an initializer list of pairs)
j[ " object " ] = { { " currency " , " USD " }, { " value " , 42.99 } };
// instead, you could also write (which looks very similar to the JSON above)
json j2 = {
{ " pi " , 3.141 },
{ " happy " , true },
{ " name " , " Niels " },
{ " nothing " , nullptr },
{ " answer " , {
{ " everything " , 42 }
}},
{ " list " , { 1 , 0 , 2 }},
{ " object " , {
{ " currency " , " USD " },
{ " value " , 42.99 }
}}
};
Обратите внимание, что во всех этих случаях вам никогда не нужно «сообщать» компилятору, какой тип значения JSON вы хотите использовать. Если вы хотите быть явным или выразить некоторые крайние случаи, вам помогут функции json::array()
и json::object()
:
// a way to express the empty array []
json empty_array_explicit = json::array();
// ways to express the empty object {}
json empty_object_implicit = json({});
json empty_object_explicit = json::object();
// a way to express an _array_ of key/value pairs [["currency", "USD"], ["value", 42.99]]
json array_not_object = json::array({ { " currency " , " USD " }, { " value " , 42.99 } });
Вы можете создать значение JSON (десериализацию), добавив _json
к строковому литералу:
// create object from string literal
json j = " { " happy " : true, " pi " : 3.141 } " _json;
// or even nicer with a raw string literal
auto j2 = R"(
{
"happy": true,
"pi": 3.141
}
)" _json;
Обратите внимание, что без добавления суффикса _json
переданный строковый литерал не анализируется, а просто используется как строковое значение JSON. То есть json j = "{ "happy": true, "pi": 3.141 }"
просто сохранит строку "{ "happy": true, "pi": 3.141 }"
, а не анализирует фактическое значение. объект.
Строковый литерал должен быть включен в область действия с using namespace nlohmann::literals;
(см. json::parse()
).
Приведенный выше пример также можно выразить явно с помощью json::parse()
:
// parse explicitly
auto j3 = json::parse( R"( {"happy": true, "pi": 3.141} )" );
Вы также можете получить строковое представление значения JSON (сериализовать):
// explicit conversion to string
std::string s = j.dump(); // {"happy":true,"pi":3.141}
// serialization with pretty printing
// pass in the amount of spaces to indent
std::cout << j.dump( 4 ) << std::endl;
// {
// "happy": true,
// "pi": 3.141
// }
Обратите внимание на разницу между сериализацией и присвоением:
// store a string in a JSON value
json j_string = " this is a string " ;
// retrieve the string value
auto cpp_string = j_string. template get<std::string>();
// retrieve the string value (alternative when a variable already exists)
std::string cpp_string2;
j_string.get_to(cpp_string2);
// retrieve the serialized value (explicit JSON serialization)
std::string serialized_string = j_string.dump();
// output of original string
std::cout << cpp_string << " == " << cpp_string2 << " == " << j_string. template get<std::string>() << ' n ' ;
// output of serialized value
std::cout << j_string << " == " << serialized_string << std::endl;
.dump()
возвращает исходное сохраненное строковое значение.
Обратите внимание, что библиотека поддерживает только UTF-8. Когда вы храните в библиотеке строки с разными кодировками, вызов dump()
может вызвать исключение, если в качестве обработчиков ошибок не используются json::error_handler_t::replace
или json::error_handler_t::ignore
.
Вы также можете использовать потоки для сериализации и десериализации:
// deserialize from standard input
json j;
std::cin >> j;
// serialize to standard output
std::cout << j;
// the setw manipulator was overloaded to set the indentation for pretty printing
std::cout << std::setw( 4 ) << j << std::endl;
Эти операторы работают для любых подклассов std::istream
или std::ostream
. Вот тот же пример с файлами:
// read a JSON file
std::ifstream i ( " file.json " );
json j;
i >> j;
// write prettified JSON to another file
std::ofstream o ( " pretty.json " );
o << std::setw( 4 ) << j << std::endl;
Обратите внимание, что установка бита исключения для failbit
не подходит для этого варианта использования. Это приведет к завершению программы из-за использования спецификатора noexcept
.
Вы также можете анализировать JSON из диапазона итераторов; то есть из любого контейнера, доступного итераторам, у которых value_type
представляет собой целочисленный тип размером 1, 2 или 4 байта, который будет интерпретироваться как UTF-8, UTF-16 и UTF-32 соответственно. Например, std::vector<std::uint8_t>
или std::list<std::uint16_t>
:
std::vector<std:: uint8_t > v = { ' t ' , ' r ' , ' u ' , ' e ' };
json j = json::parse(v.begin(), v.end());
Вы можете оставить итераторы для диапазона [begin, end):
std::vector<std:: uint8_t > v = { ' t ' , ' r ' , ' u ' , ' e ' };
json j = json::parse(v);
Поскольку функция синтаксического анализа принимает произвольные диапазоны итераторов, вы можете предоставить свои собственные источники данных, реализовав концепцию LegacyInputIterator
.
struct MyContainer {
void advance ();
const char & get_current ();
};
struct MyIterator {
using difference_type = std:: ptrdiff_t ;
using value_type = char ;
using pointer = const char *;
using reference = const char &;
using iterator_category = std::input_iterator_tag;
MyIterator& operator ++() {
target-> advance ();
return * this ;
}
bool operator !=( const MyIterator& rhs) const {
return rhs. target != target;
}
reference operator *() const {
return target-> get_current ();
}
MyContainer* target = nullptr ;
};
MyIterator begin (MyContainer& tgt) {
return MyIterator{&tgt};
}
MyIterator end ( const MyContainer&) {
return {};
}
void foo () {
MyContainer c;
json j = json::parse (c);
}
Библиотека использует SAX-подобный интерфейс со следующими функциями:
// called when null is parsed
bool null ();
// called when a boolean is parsed; value is passed
bool boolean ( bool val);
// called when a signed or unsigned integer number is parsed; value is passed
bool number_integer ( number_integer_t val);
bool number_unsigned ( number_unsigned_t val);
// called when a floating-point number is parsed; value and original string is passed
bool number_float ( number_float_t val, const string_t & s);
// called when a string is parsed; value is passed and can be safely moved away
bool string ( string_t & val);
// called when a binary value is parsed; value is passed and can be safely moved away
bool binary ( binary_t & val);
// called when an object or array begins or ends, resp. The number of elements is passed (or -1 if not known)
bool start_object (std:: size_t elements);
bool end_object ();
bool start_array (std:: size_t elements);
bool end_array ();
// called when an object key is parsed; value is passed and can be safely moved away
bool key ( string_t & val);
// called when a parse error occurs; byte position, the last token, and an exception is passed
bool parse_error (std:: size_t position, const std::string& last_token, const detail:: exception & ex);
Возвращаемое значение каждой функции определяет, следует ли продолжать анализ.
Чтобы реализовать собственный обработчик SAX, выполните следующие действия:
nlohmann::json_sax<json>
в качестве базового класса, но вы также можете использовать любой класс, в котором описанные выше функции реализованы и общедоступны.my_sax
.bool json::sax_parse(input, &my_sax)
; где первым параметром может быть любой входной параметр, например строка или входной поток, а второй параметр — указатель на ваш интерфейс SAX. Обратите внимание, что функция sax_parse
возвращает только bool
, указывающее результат последнего выполненного события SAX. Он не возвращает значение json
— вам решать, что делать с событиями SAX. Более того, в случае ошибки синтаксического анализа никакие исключения не создаются — вам решать, что делать с объектом исключения, переданным в вашу реализацию parse_error
. Внутренне интерфейс SAX используется для анализатора DOM (класс json_sax_dom_parser
), а также для приемника ( json_sax_acceptor
), см. файл json_sax.hpp
.
Мы разработали класс JSON так, чтобы он вел себя как контейнер STL. Фактически он удовлетворяет требованию ReversibleContainer .
// create an array using push_back
json j;
j.push_back( " foo " );
j.push_back( 1 );
j.push_back( true );
// also use emplace_back
j.emplace_back( 1.78 );
// iterate the array
for (json::iterator it = j.begin(); it != j.end(); ++it) {
std::cout << *it << ' n ' ;
}
// range-based for
for ( auto & element : j) {
std::cout << element << ' n ' ;
}
// getter/setter
const auto tmp = j[ 0 ]. template get<std::string>();
j[ 1 ] = 42 ;
bool foo = j.at( 2 );
// comparison
j == R"( ["foo", 1, true, 1.78] )" _json; // true
// other stuff
j.size(); // 4 entries
j.empty(); // false
j.type(); // json::value_t::array
j.clear(); // the array is empty again
// convenience type checkers
j.is_null();
j.is_boolean();
j.is_number();
j.is_object();
j.is_array();
j.is_string();
// create an object
json o;
o[ " foo " ] = 23 ;
o[ " bar " ] = false ;
o[ " baz " ] = 3.141 ;
// also use emplace
o.emplace( " weather " , " sunny " );
// special iterator member functions for objects
for (json::iterator it = o.begin(); it != o.end(); ++it) {
std::cout << it. key () << " : " << it. value () << " n " ;
}
// the same code as range for
for ( auto & el : o.items()) {
std::cout << el. key () << " : " << el. value () << " n " ;
}
// even easier with structured bindings (C++17)
for ( auto & [key, value] : o.items()) {
std::cout << key << " : " << value << " n " ;
}
// find an entry
if (o.contains( " foo " )) {
// there is an entry with key "foo"
}
// or via find and an iterator
if (o.find( " foo " ) != o.end()) {
// there is an entry with key "foo"
}
// or simpler using count()
int foo_present = o.count( " foo " ); // 1
int fob_present = o.count( " fob " ); // 0
// delete an entry
o.erase( " foo " );
Любой контейнер последовательности ( std::array
, std::vector
, std::deque
, std::forward_list
, std::list
), значения которого можно использовать для создания значений JSON (например, целые числа, числа с плавающей запятой, логические значения, строки типы или контейнеры STL, описанные в этом разделе) можно использовать для создания массива JSON. То же самое справедливо и для подобных ассоциативных контейнеров ( std::set
, std::multiset
, std::unordered_set
, std::unordered_multiset
), но в этих случаях порядок элементов массива зависит от того, как они упорядочены в массиве. соответствующий контейнер STL.
std::vector< int > c_vector { 1 , 2 , 3 , 4 };
json j_vec (c_vector);
// [1, 2, 3, 4]
std::deque< double > c_deque { 1.2 , 2.3 , 3.4 , 5.6 };
json j_deque (c_deque);
// [1.2, 2.3, 3.4, 5.6]
std::list< bool > c_list { true , true , false , true };
json j_list (c_list);
// [true, true, false, true]
std::forward_list< int64_t > c_flist { 12345678909876 , 23456789098765 , 34567890987654 , 45678909876543 };
json j_flist (c_flist);
// [12345678909876, 23456789098765, 34567890987654, 45678909876543]
std::array< unsigned long , 4 > c_array {{ 1 , 2 , 3 , 4 }};
json j_array (c_array);
// [1, 2, 3, 4]
std::set<std::string> c_set { " one " , " two " , " three " , " four " , " one " };
json j_set (c_set); // only one entry for "one" is used
// ["four", "one", "three", "two"]
std::unordered_set<std::string> c_uset { " one " , " two " , " three " , " four " , " one " };
json j_uset (c_uset); // only one entry for "one" is used
// maybe ["two", "three", "four", "one"]
std::multiset<std::string> c_mset { " one " , " two " , " one " , " four " };
json j_mset (c_mset); // both entries for "one" are used
// maybe ["one", "two", "one", "four"]
std::unordered_multiset<std::string> c_umset { " one " , " two " , " one " , " four " };
json j_umset (c_umset); // both entries for "one" are used
// maybe ["one", "two", "one", "four"]
Аналогично, любые ассоциативные контейнеры «ключ-значение» ( std::map
, std::multimap
, std::unordered_map
, std::unordered_multimap
), ключи которых могут создавать std::string
и чьи значения могут использоваться для создания значений JSON (см. примеры выше) можно использовать для создания объекта JSON. Обратите внимание, что в случае мультикарт в объекте JSON используется только один ключ, и его значение зависит от внутреннего порядка контейнера STL.
std::map<std::string, int > c_map { { " one " , 1 }, { " two " , 2 }, { " three " , 3 } };
json j_map (c_map);
// {"one": 1, "three": 3, "two": 2 }
std::unordered_map< const char *, double > c_umap { { " one " , 1.2 }, { " two " , 2.3 }, { " three " , 3.4 } };
json j_umap (c_umap);
// {"one": 1.2, "two": 2.3, "three": 3.4}
std::multimap<std::string, bool > c_mmap { { " one " , true }, { " two " , true }, { " three " , false }, { " three " , true } };
json j_mmap (c_mmap); // only one entry for key "three" is used
// maybe {"one": true, "two": true, "three": true}
std::unordered_multimap<std::string, bool > c_ummap { { " one " , true }, { " two " , true }, { " three " , false }, { " three " , true } };
json j_ummap (c_ummap); // only one entry for key "three" is used
// maybe {"one": true, "two": true, "three": true}
Библиотека поддерживает указатель JSON (RFC 6901) в качестве альтернативного средства обращения к структурированным значениям. Кроме того, JSON Patch (RFC 6902) позволяет описывать различия между двумя значениями JSON, что эффективно позволяет выполнять операции исправления и сравнения, известные из Unix.
// a JSON value
json j_original = R"( {
"baz": ["one", "two", "three"],
"foo": "bar"
} )" _json;
// access members with a JSON pointer (RFC 6901)
j_original[ " /baz/1 " _json_pointer];
// "two"
// a JSON patch (RFC 6902)
json j_patch = R"( [
{ "op": "replace", "path": "/baz", "value": "boo" },
{ "op": "add", "path": "/hello", "value": ["world"] },
{ "op": "remove", "path": "/foo"}
] )" _json;
// apply the patch
json j_result = j_original.patch(j_patch);
// {
// "baz": "boo",
// "hello": ["world"]
// }
// calculate a JSON patch from two JSON values
json::diff (j_result, j_original);
// [
// { "op":" replace", "path": "/baz", "value": ["one", "two", "three"] },
// { "op": "remove","path": "/hello" },
// { "op": "add", "path": "/foo", "value": "bar" }
// ]
Библиотека поддерживает JSON Merge Patch (RFC 7386) в качестве формата исправлений. Вместо использования указателя JSON (см. выше) для указания значений, которыми нужно манипулировать, он описывает изменения с использованием синтаксиса, который точно имитирует изменяемый документ.
// a JSON value
json j_document = R"( {
"a": "b",
"c": {
"d": "e",
"f": "g"
}
} )" _json;
// a patch
json j_patch = R"( {
"a":"z",
"c": {
"f": null
}
} )" _json;
// apply the patch
j_document.merge_patch(j_patch);
// {
// "a": "z",
// "c": {
// "d": "e"
// }
// }
Поддерживаемые типы можно неявно преобразовать в значения JSON.
Рекомендуется НЕ ИСПОЛЬЗОВАТЬ неявные преобразования ИЗ значения JSON. Более подробную информацию об этой рекомендации можно найти здесь. Вы можете отключить неявные преобразования, задав для JSON_USE_IMPLICIT_CONVERSIONS
значение 0
перед включением заголовка json.hpp
. При использовании CMake этого также можно добиться, установив для параметра JSON_ImplicitConversions
значение OFF
.
// strings
std::string s1 = " Hello, world! " ;
json js = s1;
auto s2 = js. template get<std::string>();
// NOT RECOMMENDED
std::string s3 = js;
std::string s4;
s4 = js;
// Booleans
bool b1 = true ;
json jb = b1;
auto b2 = jb. template get< bool >();
// NOT RECOMMENDED
bool b3 = jb;
bool b4;
b4 = jb;
// numbers
int i = 42