json
à partir de littéraux JSONIl existe des myriades de bibliothèques JSON, et chacune peut même avoir sa raison d'exister. Notre classe avait ces objectifs de conception :
Syntaxe intuitive . Dans des langages tels que Python, JSON ressemble à un type de données de première classe. Nous avons utilisé toute la magie des opérateurs du C++ moderne pour obtenir le même sentiment dans votre code. Consultez les exemples ci-dessous et vous saurez ce que je veux dire.
Intégration triviale . L'ensemble de notre code se compose d'un seul fichier d'en-tête json.hpp
. C'est ça. Pas de bibliothèque, pas de sous-projet, pas de dépendances, pas de système de construction complexe. La classe est écrite en Vanilla C++11. Dans l’ensemble, tout ne devrait nécessiter aucun ajustement des indicateurs de votre compilateur ou des paramètres de votre projet.
Tests sérieux . Notre code est fortement testé unitairement et couvre 100 % du code, y compris tous les comportements exceptionnels. De plus, nous avons vérifié auprès de Valgrind et des Clang Sanitizers qu'il n'y avait pas de fuite de mémoire. Google OSS-Fuzz exécute en outre des tests de fuzz sur tous les analyseurs 24h/24 et 7j/7, exécutant ainsi efficacement des milliards de tests jusqu'à présent. Pour maintenir une qualité élevée, le projet suit les meilleures pratiques de la Core Infrastructure Initiative (CII).
D’autres aspects n’étaient pas si importants pour nous :
Efficacité de la mémoire . Chaque objet JSON a une surcharge d'un pointeur (la taille maximale d'une union) et d'un élément d'énumération (1 octet). La généralisation par défaut utilise les types de données C++ suivants : std::string
pour les chaînes, int64_t
, uint64_t
ou double
pour les nombres, std::map
pour les objets, std::vector
pour les tableaux et bool
pour les booléens. Cependant, vous pouvez adapter la classe généralisée basic_json
à vos besoins.
Vitesse . Il existe certainement des bibliothèques JSON plus rapides. Cependant, si votre objectif est d'accélérer votre développement en ajoutant la prise en charge de JSON avec un seul en-tête, alors cette bibliothèque est la voie à suivre. Si vous savez utiliser un std::vector
ou std::map
, vous êtes déjà prêt.
Consultez les directives de contribution pour plus d’informations.
Vous pouvez parrainer cette bibliothèque sur GitHub Sponsors.
Merci à tous !
❓ Si vous avez une question , veuillez vérifier si elle trouve déjà une réponse dans la FAQ ou dans la section Q&R . Sinon, veuillez poser une nouvelle question ici.
Si vous souhaitez en savoir plus sur l'utilisation de la bibliothèque, consultez le reste du README , jetez un œil aux exemples de code ou parcourez les pages d'aide .
? Si vous souhaitez mieux comprendre l' API , consultez la référence API .
? Si vous avez trouvé un bug , veuillez consulter la FAQ s'il s'agit d'un problème connu ou du résultat d'une décision de conception. Veuillez également consulter la liste des problèmes avant de créer un nouveau problème . Veuillez fournir autant d'informations que possible pour nous aider à comprendre et reproduire votre problème.
Il existe également un ensemble de documents pour les navigateurs de documentation Dash, Velocity et Zeal qui contient la documentation complète sous forme de ressource hors ligne.
Voici quelques exemples pour vous donner une idée de la façon d'utiliser la classe.
Outre les exemples ci-dessous, vous souhaiterez peut-être :
→ Consultez la documentation
→ Parcourir les fichiers d'exemple autonomes
Chaque fonction API (documentée dans la documentation API) possède un fichier d'exemple autonome correspondant. Par exemple, la fonction emplace()
a un exemple de fichier emplace.cpp correspondant.
La classe json
fournit une API pour manipuler une valeur JSON. Pour créer un objet json
en lisant un fichier JSON :
# include < fstream >
# include < nlohmann/json.hpp >
using json = nlohmann::json;
// ...
std::ifstream f ( " example.json " );
json data = json::parse(f);
json
à partir de littéraux JSON Supposons que vous souhaitiez créer du code en dur cette valeur JSON littérale dans un fichier, en tant qu'objet json
:
{
"pi" : 3.141 ,
"happy" : true
}
Il existe différentes options :
// 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 },
};
Voici quelques exemples pour vous donner une idée de la façon d'utiliser la classe.
Supposons que vous souhaitiez créer l'objet JSON
{
"pi" : 3.141 ,
"happy" : true ,
"name" : " Niels " ,
"nothing" : null ,
"answer" : {
"everything" : 42
},
"list" : [ 1 , 0 , 2 ],
"object" : {
"currency" : " USD " ,
"value" : 42.99
}
}
Avec cette bibliothèque, vous pourriez écrire :
// 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 }
}}
};
Notez que dans tous ces cas, vous n'avez jamais besoin de « indiquer » au compilateur quel type de valeur JSON vous souhaitez utiliser. Si vous souhaitez être explicite ou exprimer certains cas extrêmes, les fonctions json::array()
et json::object()
vous aideront :
// 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 } });
Vous pouvez créer une valeur JSON (désérialisation) en ajoutant _json
à une chaîne littérale :
// 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;
Notez que sans ajouter le suffixe _json
, le littéral de chaîne transmis n'est pas analysé, mais simplement utilisé comme valeur de chaîne JSON. Autrement dit, json j = "{ "happy": true, "pi": 3.141 }"
stockerait simplement la chaîne "{ "happy": true, "pi": 3.141 }"
plutôt que d'analyser le réel objet.
La chaîne littérale doit être intégrée dans la portée en using namespace nlohmann::literals;
(voir json::parse()
).
L'exemple ci-dessus peut également être exprimé explicitement en utilisant json::parse()
:
// parse explicitly
auto j3 = json::parse( R"( {"happy": true, "pi": 3.141} )" );
Vous pouvez également obtenir une représentation sous forme de chaîne d'une valeur JSON (sérialiser) :
// 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
// }
Notez la différence entre la sérialisation et l'affectation :
// 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()
renvoie la valeur de chaîne stockée à l'origine.
Notez que la bibliothèque ne prend en charge que UTF-8. Lorsque vous stockez des chaînes avec des encodages différents dans la bibliothèque, l'appel de dump()
peut lever une exception à moins que json::error_handler_t::replace
ou json::error_handler_t::ignore
ne soient utilisés comme gestionnaires d'erreurs.
Vous pouvez également utiliser des flux pour sérialiser et désérialiser :
// 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;
Ces opérateurs fonctionnent pour toutes les sous-classes de std::istream
ou std::ostream
. Voici le même exemple avec des fichiers :
// 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;
Veuillez noter que la définition du bit d'exception pour failbit
est inappropriée pour ce cas d'utilisation. Cela entraînera la fin du programme en raison du spécificateur noexcept
utilisé.
Vous pouvez également analyser JSON à partir d'une plage d'itérateurs ; c'est-à-dire à partir de n'importe quel conteneur accessible par les itérateurs dont value_type
est un type intégral de 1, 2 ou 4 octets, qui sera interprété comme UTF-8, UTF-16 et UTF-32 respectivement. Par exemple, un std::vector<std::uint8_t>
, ou un std::list<std::uint16_t>
:
std::vector<std:: uint8_t > v = { ' t ' , ' r ' , ' u ' , ' e ' };
json j = json::parse(v.begin(), v.end());
Vous pouvez laisser les itérateurs pour la plage [début, fin) :
std::vector<std:: uint8_t > v = { ' t ' , ' r ' , ' u ' , ' e ' };
json j = json::parse(v);
Étant donné que la fonction d'analyse accepte des plages d'itérateurs arbitraires, vous pouvez fournir vos propres sources de données en implémentant le concept 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);
}
La bibliothèque utilise une interface de type SAX avec les fonctions suivantes :
// 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);
La valeur de retour de chaque fonction détermine si l'analyse doit se poursuivre.
Pour implémenter votre propre gestionnaire SAX, procédez comme suit :
nlohmann::json_sax<json>
comme classe de base, mais vous pouvez également utiliser n'importe quelle classe dans laquelle les fonctions décrites ci-dessus sont implémentées et publiques.my_sax
.bool json::sax_parse(input, &my_sax)
; où le premier paramètre peut être n'importe quelle entrée comme une chaîne ou un flux d'entrée et le deuxième paramètre est un pointeur vers votre interface SAX. Notez que la fonction sax_parse
renvoie uniquement un bool
indiquant le résultat du dernier événement SAX exécuté. Il ne renvoie pas de valeur json
- c'est à vous de décider quoi faire avec les événements SAX. De plus, aucune exception n'est levée en cas d'erreur d'analyse - c'est à vous de décider quoi faire avec l'objet d'exception transmis à votre implémentation parse_error
. En interne, l'interface SAX est utilisée pour l'analyseur DOM (classe json_sax_dom_parser
) ainsi que pour l'accepteur ( json_sax_acceptor
), voir fichier json_sax.hpp
.
Nous avons conçu la classe JSON pour qu'elle se comporte comme un conteneur STL. En fait, il satisfait à l’exigence 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 " );
Tout conteneur de séquence ( std::array
, std::vector
, std::deque
, std::forward_list
, std::list
) dont les valeurs peuvent être utilisées pour construire des valeurs JSON (par exemple, des entiers, des nombres à virgule flottante, des booléens, des chaînes types, ou encore les conteneurs STL décrits dans cette section) peuvent être utilisés pour créer un tableau JSON. Il en va de même pour les conteneurs associatifs similaires ( std::set
, std::multiset
, std::unordered_set
, std::unordered_multiset
), mais dans ces cas, l'ordre des éléments du tableau dépend de la façon dont les éléments sont ordonnés dans le conteneur STL respectif.
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"]
De même, tous les conteneurs clé-valeur associatifs ( std::map
, std::multimap
, std::unordered_map
, std::unordered_multimap
) dont les clés peuvent construire un std::string
et dont les valeurs peuvent être utilisées pour construire des valeurs JSON (voir exemples ci-dessus) peut être utilisé pour créer un objet JSON. Notez qu'en cas de multimaps, une seule clé est utilisée dans l'objet JSON et la valeur dépend de l'ordre interne du conteneur 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}
La bibliothèque prend en charge le pointeur JSON (RFC 6901) comme moyen alternatif pour traiter les valeurs structurées. De plus, JSON Patch (RFC 6902) permet de décrire les différences entre deux valeurs JSON, permettant ainsi les opérations de patch et de diff connues sous 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" }
// ]
La bibliothèque prend en charge JSON Merge Patch (RFC 7386) comme format de correctif. Au lieu d'utiliser le pointeur JSON (voir ci-dessus) pour spécifier les valeurs à manipuler, il décrit les modifications à l'aide d'une syntaxe qui imite fidèlement le document en cours de modification.
// 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"
// }
// }
Les types pris en charge peuvent être implicitement convertis en valeurs JSON.
Il est recommandé de NE PAS UTILISER de conversions implicites À PARTIR d'une valeur JSON. Vous pouvez trouver plus de détails sur cette recommandation ici. Vous pouvez désactiver les conversions implicites en définissant JSON_USE_IMPLICIT_CONVERSIONS
sur 0
avant d'inclure l'en-tête json.hpp
. Lorsque vous utilisez CMake, vous pouvez également y parvenir en définissant l'option JSON_ImplicitConversions
sur 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