json
-Objekten aus JSON-LiteralenEs gibt unzählige JSON-Bibliotheken, und jede hat möglicherweise sogar ihre Daseinsberechtigung. Unsere Klasse hatte diese Designziele:
Intuitive Syntax . In Sprachen wie Python fühlt sich JSON wie ein erstklassiger Datentyp an. Wir haben die ganze Operator-Magie des modernen C++ genutzt, um das gleiche Gefühl in Ihrem Code zu erreichen. Schauen Sie sich die Beispiele unten an und Sie werden wissen, was ich meine.
Triviale Integration . Unser gesamter Code besteht aus einer einzigen Header-Datei json.hpp
. Das ist es. Keine Bibliothek, kein Unterprojekt, keine Abhängigkeiten, kein komplexes Build-System. Die Klasse ist in Vanilla C++11 geschrieben. Alles in allem sollte alles keine Anpassung Ihrer Compiler-Flags oder Projekteinstellungen erfordern.
Ernsthafte Tests . Unser Code wird umfassend Unit-Tests unterzogen und deckt 100 % des Codes ab, einschließlich aller außergewöhnlichen Verhaltensweisen. Darüber hinaus haben wir bei Valgrind und den Clang Sanitizers überprüft, dass keine Speicherlecks vorliegen. Google OSS-Fuzz führt außerdem rund um die Uhr Fuzz-Tests für alle Parser durch und führt so bisher effektiv Milliarden von Tests aus. Um eine hohe Qualität aufrechtzuerhalten, folgt das Projekt den Best Practices der Core Infrastructure Initiative (CII).
Andere Aspekte waren uns nicht so wichtig:
Speichereffizienz . Jedes JSON-Objekt hat einen Overhead von einem Zeiger (die maximale Größe einer Union) und einem Aufzählungselement (1 Byte). Die Standardgeneralisierung verwendet die folgenden C++-Datentypen: std::string
für Zeichenfolgen, int64_t
, uint64_t
oder double
für Zahlen, std::map
für Objekte, std::vector
für Arrays und bool
für boolesche Werte. Sie können jedoch die verallgemeinerte Klasse basic_json
entsprechend Ihren Anforderungen als Vorlage verwenden.
Geschwindigkeit . Es gibt sicherlich schnellere JSON-Bibliotheken. Wenn Ihr Ziel jedoch darin besteht, Ihre Entwicklung zu beschleunigen, indem Sie JSON-Unterstützung mit einem einzigen Header hinzufügen, dann ist diese Bibliothek die richtige Wahl. Wenn Sie wissen, wie man einen std::vector
oder std::map
verwendet, sind Sie bereits gerüstet.
Weitere Informationen finden Sie in den Beitragsrichtlinien.
Sie können diese Bibliothek bei GitHub Sponsors sponsern.
Vielen Dank an alle!
❓ Wenn Sie eine Frage haben, prüfen Sie bitte, ob diese bereits in den FAQ oder im Abschnitt „Fragen und Antworten“ beantwortet wurde. Wenn nicht, stellen Sie bitte dort eine neue Frage .
Wenn Sie mehr über die Verwendung der Bibliothek erfahren möchten, lesen Sie den Rest der README-Datei , werfen Sie einen Blick auf Codebeispiele oder stöbern Sie durch die Hilfeseiten .
? Wenn Sie die API besser verstehen möchten, schauen Sie sich die API-Referenz an.
? Wenn Sie einen Fehler gefunden haben, schauen Sie bitte in den FAQ nach, ob es sich um ein bekanntes Problem oder das Ergebnis einer Designentscheidung handelt. Bitte werfen Sie auch einen Blick auf die Issue-Liste, bevor Sie ein neues Issue erstellen . Bitte geben Sie so viele Informationen wie möglich an, damit wir Ihr Problem verstehen und reproduzieren können.
Für die Dokumentationsbrowser Dash, Velocity und Zeal gibt es außerdem ein Docset , das die vollständige Dokumentation als Offline-Ressource enthält.
Hier sind einige Beispiele, um Ihnen eine Vorstellung davon zu geben, wie Sie die Klasse verwenden können.
Neben den folgenden Beispielen möchten Sie vielleicht:
→ Prüfen Sie die Dokumentation
→ Durchsuchen Sie die eigenständigen Beispieldateien
Für jede API-Funktion (dokumentiert in der API-Dokumentation) gibt es eine entsprechende eigenständige Beispieldatei. Beispielsweise verfügt die Funktion emplace()
über eine passende Beispieldatei emplace.cpp.
Die json
-Klasse stellt eine API zum Bearbeiten eines JSON-Werts bereit. So erstellen Sie ein json
Objekt durch Lesen einer JSON-Datei:
# include < fstream >
# include < nlohmann/json.hpp >
using json = nlohmann::json;
// ...
std::ifstream f ( " example.json " );
json data = json::parse(f);
json
-Objekten aus JSON-Literalen Angenommen, Sie möchten diesen literalen JSON-Wert in einer Datei als json
-Objekt fest codieren:
{
"pi" : 3.141 ,
"happy" : true
}
Es gibt verschiedene Möglichkeiten:
// 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 },
};
Hier sind einige Beispiele, um Ihnen eine Vorstellung davon zu geben, wie Sie die Klasse verwenden können.
Angenommen, Sie möchten das JSON-Objekt erstellen
{
"pi" : 3.141 ,
"happy" : true ,
"name" : " Niels " ,
"nothing" : null ,
"answer" : {
"everything" : 42
},
"list" : [ 1 , 0 , 2 ],
"object" : {
"currency" : " USD " ,
"value" : 42.99
}
}
Mit dieser Bibliothek könnten Sie schreiben:
// 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 }
}}
};
Beachten Sie, dass Sie in all diesen Fällen dem Compiler niemals „mitteilen“ müssen, welchen JSON-Werttyp Sie verwenden möchten. Wenn Sie explizit sein oder einige Randfälle ausdrücken möchten, helfen die Funktionen json::array()
und 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 } });
Sie können einen JSON-Wert (Deserialisierung) erstellen, indem Sie _json
an ein Zeichenfolgenliteral anhängen:
// 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;
Beachten Sie, dass ohne Anhängen des Suffixes _json
das übergebene String-Literal nicht geparst, sondern nur als JSON-String-Wert verwendet wird. Das heißt, json j = "{ "happy": true, "pi": 3.141 }"
würde einfach die Zeichenfolge "{ "happy": true, "pi": 3.141 }"
speichern, anstatt die tatsächliche Zeichenfolge zu analysieren Objekt.
Das String-Literal sollte using namespace nlohmann::literals;
(siehe json::parse()
).
Das obige Beispiel kann auch explizit mit json::parse()
ausgedrückt werden:
// parse explicitly
auto j3 = json::parse( R"( {"happy": true, "pi": 3.141} )" );
Sie können auch eine Zeichenfolgendarstellung eines JSON-Werts abrufen (serialisieren):
// 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
// }
Beachten Sie den Unterschied zwischen Serialisierung und Zuweisung:
// 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()
gibt den ursprünglich gespeicherten String-Wert zurück.
Beachten Sie, dass die Bibliothek nur UTF-8 unterstützt. Wenn Sie Zeichenfolgen mit unterschiedlichen Codierungen in der Bibliothek speichern, kann der Aufruf von dump()
eine Ausnahme auslösen, es sei denn, json::error_handler_t::replace
oder json::error_handler_t::ignore
werden als Fehlerhandler verwendet.
Sie können Streams auch zum Serialisieren und Deserialisieren verwenden:
// 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;
Diese Operatoren funktionieren für alle Unterklassen von std::istream
oder std::ostream
. Hier ist das gleiche Beispiel mit Dateien:
// 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;
Bitte beachten Sie, dass das Setzen des Ausnahmebits für failbit
für diesen Anwendungsfall ungeeignet ist. Aufgrund des verwendeten noexcept
Spezifizierers führt dies zum Programmabbruch.
Sie können JSON auch aus einem Iteratorbereich analysieren. Das heißt, aus jedem Container, auf den Iteratoren zugreifen können, dessen value_type
ein ganzzahliger Typ von 1, 2 oder 4 Byte ist, der als UTF-8, UTF-16 bzw. UTF-32 interpretiert wird. Zum Beispiel ein std::vector<std::uint8_t>
oder ein std::list<std::uint16_t>
:
std::vector<std:: uint8_t > v = { ' t ' , ' r ' , ' u ' , ' e ' };
json j = json::parse(v.begin(), v.end());
Sie können die Iteratoren für den Bereich [begin, end] belassen:
std::vector<std:: uint8_t > v = { ' t ' , ' r ' , ' u ' , ' e ' };
json j = json::parse(v);
Da die Parse-Funktion beliebige Iteratorbereiche akzeptiert, können Sie Ihre eigenen Datenquellen bereitstellen, indem Sie das LegacyInputIterator
Konzept implementieren.
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);
}
Die Bibliothek nutzt eine SAX-ähnliche Schnittstelle mit folgenden Funktionen:
// 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);
Der Rückgabewert jeder Funktion bestimmt, ob die Analyse fortgesetzt werden soll.
Um Ihren eigenen SAX-Handler zu implementieren, gehen Sie wie folgt vor:
nlohmann::json_sax<json>
als Basisklasse verwenden, Sie können aber auch jede Klasse verwenden, in der die oben beschriebenen Funktionen implementiert und öffentlich sind.my_sax
.bool json::sax_parse(input, &my_sax)
; Dabei kann der erste Parameter eine beliebige Eingabe wie eine Zeichenfolge oder ein Eingabestream sein und der zweite Parameter ist ein Zeiger auf Ihre SAX-Schnittstelle. Beachten Sie, dass die Funktion sax_parse
nur einen bool
zurückgibt, der das Ergebnis des zuletzt ausgeführten SAX-Ereignisses angibt. Es wird kein json
-Wert zurückgegeben. Es liegt an Ihnen, zu entscheiden, was mit den SAX-Ereignissen geschehen soll. Darüber hinaus werden im Falle eines Parse-Fehlers keine Ausnahmen ausgelöst. Es liegt an Ihnen, was mit dem an Ihre parse_error
Implementierung übergebenen Ausnahmeobjekt geschehen soll. Intern wird die SAX-Schnittstelle für den DOM-Parser (Klasse json_sax_dom_parser
) sowie den Akzeptor ( json_sax_acceptor
) verwendet, siehe Datei json_sax.hpp
.
Wir haben die JSON-Klasse so entworfen, dass sie sich wie ein STL-Container verhält. Tatsächlich erfüllt es die ReversibleContainer- Anforderung.
// 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 " );
Jeder Sequenzcontainer ( std::array
, std::vector
, std::deque
, std::forward_list
, std::list
), dessen Werte zum Erstellen von JSON-Werten verwendet werden können (z. B. Ganzzahlen, Gleitkommazahlen, Boolesche Werte, Zeichenfolgen). (Typen oder wiederum in diesem Abschnitt beschriebene STL-Container) können zum Erstellen eines JSON-Arrays verwendet werden. Das Gleiche gilt für ähnliche assoziative Container ( std::set
, std::multiset
, std::unordered_set
, std::unordered_multiset
), aber in diesen Fällen hängt die Reihenfolge der Elemente des Arrays davon ab, wie die Elemente im Array angeordnet sind jeweiligen STL-Container.
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"]
Ebenso alle assoziativen Schlüsselwertcontainer ( std::map
, std::multimap
, std::unordered_map
, std::unordered_multimap
), deren Schlüssel einen std::string
erstellen können und deren Werte zum Erstellen von JSON-Werten verwendet werden können (siehe Beispiele oben) können zum Erstellen eines JSON-Objekts verwendet werden. Beachten Sie, dass bei Multimaps nur ein Schlüssel im JSON-Objekt verwendet wird und der Wert von der internen Reihenfolge des STL-Containers abhängt.
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}
Die Bibliothek unterstützt JSON Pointer (RFC 6901) als alternative Möglichkeit, strukturierte Werte anzusprechen. Darüber hinaus ermöglicht der JSON-Patch (RFC 6902) die Beschreibung von Unterschieden zwischen zwei JSON-Werten und ermöglicht so effektiv Patch- und Diff-Operationen, die von Unix bekannt sind.
// 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" }
// ]
Die Bibliothek unterstützt JSON Merge Patch (RFC 7386) als Patchformat. Anstatt den JSON-Zeiger (siehe oben) zu verwenden, um zu manipulierende Werte anzugeben, werden die Änderungen mithilfe einer Syntax beschrieben, die das zu ändernde Dokument genau nachahmt.
// 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"
// }
// }
Unterstützte Typen können implizit in JSON-Werte konvertiert werden.
Es wird empfohlen , KEINE impliziten Konvertierungen VON einem JSON-Wert zu VERWENDEN. Weitere Details zu dieser Empfehlung finden Sie hier. Sie können implizite Konvertierungen deaktivieren, indem Sie JSON_USE_IMPLICIT_CONVERSIONS
auf 0
definieren, bevor Sie den json.hpp
Header einschließen. Bei Verwendung von CMake können Sie dies auch erreichen, indem Sie die Option JSON_ImplicitConversions
auf OFF
setzen.
// 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