La bibliothèque JSON Link est une bibliothèque JSON C++ hautes performances, sans allocation, prenant en charge :
La bibliothèque prend en charge d'autres modes d'analyse qui peuvent également être mélangés.
json_array_iterator
ou json_array_range
.json_value
qui permet l'itération/l'analyse paresseuse du documentCertaines autres caractéristiques notables sont :
boost::multiprecision::cpp_int
ou GNU BigNum/Rational mpq_t
La bibliothèque utilise la licence BSL
Lorsque la structure du document JSON est connue, l'analyse ressemble à ce qui suit :
MyThing thing = daw::json::from_json<MyThing>( json_string );
ou pour les documents matriciels, où la racine du document est un tableau, il existe une méthode d'assistance pour faciliter les choses, et elle peut être analysée comme suit :
std::vector<MyThing> things = daw::json::from_json_array<MyThing>( json_string2 );
Si la structure du document JSON est inconnue, on peut construire une json_value
qui agit comme une vue et permet l'itération et l'analyse pull à la demande. Voici un exemple d'ouverture d'un json_value
à partir de données JSON :
json_value val = daw::json::json_value( json_string );
Les méthodes from_json
et to_json
permettent d'accéder à la plupart des besoins d'analyse et de sérialisation.
L'analyseur basé sur les événements (SAX) peut être appelé via daw::json::json_event_parser
. Il faut deux arguments, un document json et un gestionnaire d'événements. Le gestionnaire d'événements peut participer à des événements en ayant l'un des membres suivants :
Le mappage de vos classes vers les documents JSON se fait en spécialisant le trait daw::json::json_data_contract
. Une classe mappée n’a pas besoin d’être à nouveau mappée si elle est membre d’une autre classe mappée. Le trait json_data_contract
comporte deux parties. La première est un alias de type nommé type
qui mappe les membres JSON au constructeur de notre classe. Cela évite d'avoir besoin d'un accès privé à la classe, en supposant que les données que nous sérialiserions seraient également nécessaires pour construire la classe. Par exemple:
struct Thing {
int a;
int b;
};
La construction de Thing
nécessite 2 entiers et si nous avions le JSON suivant :
{
"a" : 42 ,
"b" : 1234
}
Nous pourrions faire le mappage comme suit :
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 >
>;
};
}
Cela indique que la classe JSON, dans le document, aura au moins deux membres « a » et « b » qui seront des nombres entiers. Ils seront transmis au constructeur de Thing
lorsque daw::json::from_json<Thing>( json_doc );
est appelée, ou qu'une autre classe a un mappage de membre json_class<MemberName, Thing>
. Ce qui précède est la méthode de mappage C++17 pour les noms, elle fonctionne également dans les futures versions C++. Mais, en C++20 et versions ultérieures, les noms peuvent être intégrés dans le mappage, par exemple json_number<"a", int>
. Ce qui précède est tout ce qui est nécessaire pour analyser JSON, car la sérialisation d'une fonction membre statique est nécessaire dans le trait. En prenant l'exemple précédent et en l'étendant, nous pourrions sérialiser Thing
avec :
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 );
}
}
L'ordre des membres renvoyés sous forme de tuple doit correspondre au mappage dans le type alias type
. Cela permet également de transmettre le résultat des méthodes d'accès, si les données membres ne sont pas publiques. De plus, la classe Thing
doit être constructible à partir de int, int
. La bibliothèque prend en charge à la fois les constructeurs réguliers et l'initialisation agrégée ( Thing{ int, int }
et Thing( int, int )
) en C++17.
to_json_data
n'a pas besoin de renvoyer un tuple de références aux membres d'objet existants, mais peut également renvoyer des valeurs calculées. Il ne laisse pas passer les valeurs car elles sont souvent temporaires et peut entraîner un débogage longue distance. La bibliothèque va static_assert à ce sujet et suggérera d'inclure <daw/daw_tuple_forward.h>
et d'appeler daw::forward_nonrvalue_as_tuple( ... )
qui stocke les temporaires et transmet d'autres types de référence. Les analyseurs fonctionnent en construisant chaque argument sur place dans l'appel au constructeur de la classe. Les analyseurs d'arguments individuels peuvent être adaptés aux circonstances spécifiées des données (par exemple, nombres à virgule flottante et nombres entiers). Ensuite, avec notre trait de type définissant les arguments nécessaires pour construire la classe C++ et leur ordre, nous pouvons examiner chaque membre du JSON. Maintenant, nous construisons la valeur avec le résultat de chaque analyseur ; similaire à T{ parse<0, json_string<"name">>( data ), parse<1, json_number<"age", unsigned>>( data ), parse<json_number<2, "number>>( data )}
. Pour chaque membre, le flux de données avancera jusqu'à ce que nous trouvions le membre que nous devons analyser, stockant les emplacements intéressés pour une analyse ultérieure. Ce processus nous permet également d'analyser d'autres classes en tant que membres via le. json_class<"member_name", Type>
type de mappage Pour que chaque trait de mappage ne traite que ses membres spécifiques et non leurs détails.
Dans des contextes sans nom, tels que la valeur racine, les éléments du tableau, certains types de valeurs clés et les listes d'éléments variantes où le nom serait no_name
, on peut utiliser certains types de données C++ natifs au lieu des types de mappage JSON. Cela inclut les entiers, les virgules flottantes, les booléens, std::string, std::string_view, les conteneurs associatifs, les conteneurs de séquence, les types similaires Nullable/Facultatif et les classes précédemment mappées.
Par exemple, pour mapper un tableau de chaînes.
template <>
struct daw ::json::json_data_contract<MyType> {
using type = json_member_list<json_array< " member_name " , std::string>>;
};
On peut utiliser vcpkg pour récupérer la dernière version, le port s'appelle daw-json-link
find_package ( daw-json-link )
#...
target_link_libraries ( MyTarget daw::daw-json-link )
La bibliothèque est uniquement un en-tête et peut être clonée, avec ses deux dépendances, suivie de l'ajout des sous-dossiers include/
de chacun au chemin d'inclusion du compilateur.
Pour utiliser daw_json_link dans vos projets cmake, l'ajout de ce qui suit devrait lui permettre de l'intégrer avec les dépendances :
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 )
Sur un système avec bash, c'est similaire sur d'autres systèmes également, ce qui suit peut être installé pour le système
git clone https://github.com/beached/daw_json_link
cd daw_json_link
mkdir build
cd build
cmake ..
cmake --install .
Cela permettra une installation cmake find_package ou son utilisation comme en-tête régulier tant que le dossier d'inclusion du préfixe d'installation est inclus dans les chemins d'inclusion du compilateur.
Ce qui suit va construire et exécuter les tests.
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 .
Après la construction, les exemples individuels peuvent également être testés. city_test_bin
nécessite le chemin d’accès au fichier JSON des villes.
./tests/city_test_bin ../test_data/cities.json
L'ordre des membres dans les structures de données doit généralement correspondre à celui des données JSON, si possible. L'analyseur est plus rapide s'il n'a pas besoin de revenir en arrière pour les valeurs. Les valeurs facultatives, lorsqu'elles sont manquantes dans les données JSON, peuvent également ralentir l'analyse. Si possible, faites-les envoyer comme nuls. L'analyseur n'alloue pas. Les types de données analysés peuvent le faire, ce qui permet d'utiliser des allocateurs personnalisés ou un mélange car leurs structures de données effectueront l'allocation. La valeur par défaut pour les tableaux consiste à utiliser std::vector et si cela n'est pas souhaitable, vous devez fournir le type.
Actuellement, la bibliothèque ne déséchappe pas les noms de membres lors de la sérialisation, ils sont censés être valides et non échappés. Cela pourrait être un futur ajout facultatif, car cela a un coût.
Il existe de légères différences entre C++17 et C++20, où C++20 autorise certains codes non disponibles en 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>>;
};
}
Les deux versions de C++ prennent en charge cette méthode pour nommer les membres.
Lorsqu'ils sont compilés dans le compilateur C++20, en plus de transmettre un char const *
comme en C++17, les noms de membres peuvent être spécifiés directement sous forme de chaînes littérales. La prise en charge du compilateur C++20 est encore très précoce et voici les dragons. Il existe des problèmes connus avec g++9.x en mode C++20, et il n'est testé qu'avec g++10/11. Voici les dragons
namespace daw ::json {
template <>
struct json_data_contract <MyType> {
using type = json_member_list<json_number< " member_name " >>;
};
}
Une fois qu'un type de données a été mappé avec un json_data_contract
, la bibliothèque fournit des méthodes pour les analyser JSON.
MyClass my_class = from_json<MyClass>( json_str );
Alternativement, si l'entrée est fiable, la version la moins vérifiée peut être plus rapide
MyClass my_class = from_json<MyClass, options::parse_flags<options::CheckedParseMode::no>>( json_str );
Les documents JSON avec la racine du tableau utilisent la fonction from_json_array
pour analyser
std::vector<MyClass> my_data = from_json_array<MyClass>( json_str );
Alternativement, si l'entrée est fiable, la version la moins vérifiée peut être plus rapide
std::vector<MyClass> my_data = from_json_array<MyClass, std::vector<MyClass>, options::parse_flags<options::CheckedParseMode::no>>( json_str );
json_array_iterator
Si vous souhaitez travailler à partir de données de tableau JSON, vous pouvez obtenir un itérateur et utiliser les algorithmes std pour itérer sur les tableaux dans les données JSON via json_array_iterator
using iterator_t = json_array_iterator<MyClass>;
auto pos = std::find( iterator_t ( json_str ), iterator_t ( ), MyClass( ... ) );
Alternativement, si l'entrée est fiable, vous pouvez appeler la version la moins vérifiée
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
Pour un DOM comme l'API, souvent utilisé pour des choses comme les interfaces graphiques et fournissant du code lorsque les mappages sont inadéquats, on peut utiliser json_value
. Ceci est utilisé dans l'outil json_to_cpp.
auto jv = daw::json::json_value( json_doc );
On peut utiliser un chemin JSON pour extraire un entier
int foo = as< int >( jv[ " path.to.int " ] );
Ici, "path.to.int"
est un chemin JSON qui représente l'exploration d'une classe JSON comme
{
"path" : {
"to" : {
"int" : 5
}
}
}
On peut également sélectionner via une syntaxe de type tableau dans le chemin JSON, "path[5]"
sélectionnerait le 5ème élément/membre de "path"
. Si vous souhaitez sérialiser en JSON. La syntaxe JSON Path fonctionne également avec from_json
, from_json_array
et json_array_iterator
.
to_json
std::string my_json_data = to_json( MyClass{} );
Ou sérialisez un tableau, une collection, une plage ou une vue d'objets. Nécessite uniquement std::begin(...)
et std::end(...)
pour fonctionner pour le type. Cela permet la sérialisation lorsque le type n'est pas un ensemble d'éléments constructibles.
std::vector<MyClass> arry = ...;
std::string my_json_data = to_json_array( arry );
L'analyse des erreurs consiste par défaut à lancer une daw::json::json_exception
qui inclut des informations sur la raison et l'emplacement de l'échec.
Si les exceptions sont désactivées, la bibliothèque appellera std::terminate
en cas d'erreur d'analyse par défaut.
Tandis que la gestion des erreurs consiste par défaut à lancer une daw::json::json_exception
en cas d'erreurs, ou à appeler std::terminate
si les exceptions sont désactivées. On peut modifier ce comportement en définissant le pointeur de fonction daw::json::daw_json_error_handler
. La seule condition est que la fonction ne retourne pas. Un exemple qui utilise ceci est dans error_handling_bench_test.cpp
La vérification des erreurs peut être modifiée analyse par analyse. from_json
, from_json_array
, json_value
, json_array_iterator
et tous prennent en charge les options d'analyse. les appels peuvent être fournis avec une option d'analyseur. Les options disponibles sont documentées dans l'élément du livre de recettes parser_policies.
daw::json::json_exception
a une fonction membre std::string_view reason( ) const
semblable à std::exception
's what( )
mais renvoie un std::string
avec plus de contexte que what( )
. Si vous souhaitez désactiver les exceptions dans un environnement qui en possède, vous pouvez définir DAW_JSON_DONT_USE_EXCEPTIONS
pour désactiver le lancement d'exceptions par la bibliothèque ou définir le gestionnaire, cela n'est plus recommandé car le gestionnaire peut être défini sur l'une des deux valeurs par défaut daw::json::default_error_handling_throwing
ou daw::json::default_error_handling_terminating
.
Cela peut être accompli en écrivant une spécialisation de json_data_contract
dans l'espace de noms daw::json
. Par exemple:
# 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);
}
Voir sur l'Explorateur du compilateur
Les constructeurs d'agrégats et d'utilisateurs sont pris en charge. La description fournit les valeurs nécessaires pour construire votre type et l'ordre. L'ordre spécifié est l'ordre dans lequel ils sont placés dans le constructeur. Il existe également des points de personnalisation permettant de créer votre type. Une classe comme :
# 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 " >
>;
};
}
Fonctionne aussi. Idem mais C++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>
>;
};
}
Les descriptions de classes sont récursives avec leurs sous-membres. En utilisant la AggClass
précédente, on peut l'inclure en tant que membre d'une autre classe
// 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>
>;
};
}
Ce qui précède mappe une classe MyClass
qui a une autre classe décrite AggClass. En outre, vous pouvez voir que les noms de membres de la classe C++ ne doivent pas nécessairement correspondre à ceux des noms JSON mappés et que les chaînes peuvent utiliser std::string_view
comme type de résultat. Il s'agit d'une amélioration importante des performances si vous pouvez garantir que le tampon contenant le fichier JSON existera aussi longtemps que la classe.
Itération sur des tableaux JSON. L'itérateur d'entrée daw::json::json_array_iterator<JsonElement>
permet d'itérer sur le tableau d'éléments JSON. Il s'agit techniquement d'un itérateur d'entrée mais peut être stocké et réutilisé comme un itérateur avant. Il ne renvoie pas une référence mais une valeur.
# 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 " ;
}
}
L'analyse peut commencer au niveau d'un membre ou d'un élément spécifique. Un chemin de membre facultatif vers from_json
, from_json_array
, json_value
, json_array_iterator
et similaires peut être spécifié. Le format est une liste de noms de membres séparés par des points et éventuellement un index de tableau tel que member0.member1
qui revient à analyser :
{
"member0" : {
"member1" : {}
}
}
ou member0[5].member1
qui commencerait l'analyse à "member1" dans un document comme :
{
"member0" : [
" a " ,
" b " ,
" c " ,
" d " ,
" e " ,
{
"member1" : " "
}
]
}
ou
{
"member0" : {
"a" : " " ,
"b" : " " ,
"c" : " " ,
"d" : " " ,
"e" : " " ,
"f" : {
"member1" : " "
}
}
}
Les commentaires sont pris en charge lorsque la stratégie d’analyseur correspondante est utilisée. Actuellement, il existe deux formes de politiques de commentaires.
//
commentaires de ligne et style C /* */
commentaires. { // This is a comment
"a" /*this is also a comment*/: "a's value"
}
#
ligne de commentaires { # This is a comment
"a" #this is also a comment
: "a's value"
}
La politique de commentaires peut être définie via PolicyCommentTypes
. Voir parser_policies pour plus d'informations.
Pour activer la sérialisation, vous devez créer une fonction statique supplémentaire dans votre spécialisation de json_data_contract
appelée to_json_data( Thing const & );
qui renvoie un tuple de membres. Il fournira un mappage de votre type aux arguments fournis dans la description de la classe. Pour sérialiser en une chaîne JSON, on appelle to_json( my_thing );
où my_thing
est un type enregistré ou l'un des types fondamentaux comme les conteneurs, les cartes, les chaînes, les booléens et les nombres. Le résultat de la méthode statique to_json_data( Thing const & )
est un tuple
dont les éléments correspondent à l'ordre dans le type
d'alias de type json_data_contract
qui l'accompagne. En raison de la manière dont la méthode est utilisée, les tuples avec des éléments rvalue entraîneront un bug d'utilisation après destruction. Le compilateur générera une erreur si cela se produit. Inclure <daw/daw_tuple_forward.h>
et la méthode daw::forward_nonrvalue_as_tuple
stockera les rvalues au lieu de les transmettre par référence. C'est souvent le résultat d'éléments de tuple calculés. En utilisant l'exemple ci-dessus, ajoutons une méthode 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 );
Alternativement, on peut sortir vers n'importe quel type WritableOutput, par défaut cela inclut FILE*, iostreams, conteneurs de caractères et pointeurs de caractères. Dans votre type json_data_constract
. Ou si vous l'avez accepté, vous pouvez obtenir un opérateur ostream<< pour votre type qui insère le json dans le flux de sortie en ajoutant un alias de type nommé opt_into_iostreams
le type qu'il alias n'a pas d'importance et inclut daw/json/daw_json_iostream.h
. Par exemple
# 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 ' ;
Un exemple fonctionnel peut être trouvé sur daw_json_iostream_test.cpp ou sur l'explorateur du compilateur
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>
Il existe quelques définitions qui affectent le fonctionnement de JSON Link
DAW_JSON_DONT_USE_EXCEPTIONS
- Contrôle si les exceptions sont autorisées. Si ce n'est pas le cas, un std::terminate()
sur les erreurs se produira. Ceci est automatique si les exceptions sont désactivées (par exemple -fno-exceptions
)DAW_ALLOW_SSE42
- Autoriser le mode expérimental SSE42, généralement le mode constexpr est plus rapideDAW_JSON_NO_CONST_EXPR
- Ceci peut être utilisé pour permettre aux classes sans membres spéciaux de déplacement/copie d'être construites à partir de données JSON antérieures à C++ 20. Ce mode ne fonctionne pas dans une expression constante antérieure à C++20 lorsque cet indicateur n'est plus nécessaire. Les compilateurs plus anciens peuvent toujours fonctionner, mais lors des tests, certains ont entraîné des erreurs ICE ou de compilation en raison d'une prise en charge boguée de C++17. Souvent, ne pas utiliser constexpr peut aussi aider.
json_key_value
.std::multimap<std::string, T>
ou std::vector<std::pair<std::string, T>>
tous les membres sont conservés avec les premiers dans l'ordre. Alternativement, le type json_value
permettra une itération sur les membres de la classe et une analyse paresseuse du bon. Voir Valeurs clés du livre de recettes qui illustre ces méthodes.