La biblioteca JSON Link es una biblioteca JSON de C++ de alto rendimiento y sin asignación que admite:
La biblioteca admite otros modos de análisis que también se pueden mezclar.
json_array_iterator
o json_array_range
.json_value
que permite la iteración/análisis diferido del documento.Algunas otras características notables son:
boost::multiprecision::cpp_int
o GNU BigNum/Rational mpq_t
La biblioteca está utilizando la licencia BSL.
Cuando se conoce la estructura del documento JSON, el análisis es como el siguiente:
MyThing thing = daw::json::from_json<MyThing>( json_string );
o para documentos de matriz, donde la raíz del documento es una matriz, existe un método auxiliar para hacerlo más fácil y se puede analizar de la siguiente manera:
std::vector<MyThing> things = daw::json::from_json_array<MyThing>( json_string2 );
Si se desconoce la estructura del documento JSON, se puede construir un json_value
que actúe como una vista y permita la iteración y el análisis de extracción bajo demanda. El siguiente es un ejemplo de cómo abrir un json_value
a partir de datos JSON:
json_value val = daw::json::json_value( json_string );
Los métodos from_json
y to_json
permiten acceder a la mayoría de las necesidades de análisis y serialización.
El analizador basado en eventos (SAX) se puede llamar a través de daw::json::json_event_parser
. Se necesitan dos argumentos, un documento json y un controlador de eventos. El controlador de eventos puede optar por participar en eventos si tiene cualquiera de los siguientes miembros:
El mapeo de sus clases a documentos JSON se realiza especializando el rasgo daw::json::json_data_contract
. Una clase asignada no necesita ser asignada nuevamente si es miembro de otra clase asignada. Hay dos partes en el rasgo json_data_contract
, la primera es un alias de tipo llamado type
que asigna los miembros JSON al constructor de nuestra clase. Esto evita la necesidad de acceso privado a la clase, asumiendo que los datos que serializaríamos también serían necesarios para construir la clase. Por ejemplo:
struct Thing {
int a;
int b;
};
La construcción de Thing
requiere 2 números enteros y si tuviéramos el siguiente JSON:
{
"a" : 42 ,
"b" : 1234
}
Podríamos hacer el mapeo de la siguiente manera:
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 >
>;
};
}
Esto dice que la clase JSON, en el documento, tendrá al menos dos miembros "a" y "b", que serán números enteros. Se pasarán al constructor de Thing
cuando daw::json::from_json<Thing>( json_doc );
se llama, o que otra clase tiene una asignación de miembros json_class<MemberName, Thing>
. Lo anterior es el método de mapeo de C++17 para los nombres; también funciona en futuras versiones de C++. Pero, en C++ 20 y versiones posteriores, los nombres pueden estar en línea en el mapeo, por ejemplo, json_number<"a", int>
. Lo anterior es todo lo que se necesita para analizar JSON; para serializar se necesita una función miembro estática en el rasgo. Tomando el ejemplo anterior y extendiéndolo podríamos serializar Thing
con:
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 );
}
}
El orden de los miembros devueltos como una tupla debe coincidir con la asignación en el type
de alias tipo. Esto también permite pasar el resultado de los métodos de acceso, si los miembros de los datos no son públicos. Además, la clase Thing
debe poder construirse a partir de int, int
. La biblioteca admite constructores regulares e init agregado ( Thing{ int, int }
y Thing( int, int )
) en C++17.
to_json_data
no tiene que devolver una tupla de referencias a los miembros del objeto existente, pero también puede devolver valores calculados. No permite el paso de valores, ya que a menudo son temporales y puede resultar en una depuración a larga distancia. La biblioteca hará static_assert sobre esto y sugerirá incluir <daw/daw_tuple_forward.h>
y llamar a daw::forward_nonrvalue_as_tuple( ... )
que almacena temporales y reenvía otros tipos de referencia. Los analizadores funcionan construyendo cada argumento en el lugar de la llamada al constructor de la clase. Los analizadores de argumentos individuales se pueden ajustar para las circunstancias específicas de los datos (por ejemplo, números de punto flotante y enteros). Luego, con nuestro rasgo de tipo que define los argumentos necesarios para construir la clase C++ y su orden, podemos observar cada miembro en el JSON. Ahora construimos el valor con el resultado de cada analizador; similar a T{ parse<0, json_string<"name">>( data ), parse<1, json_number<"age", unsigned>>( data ), parse<json_number<2, "number>>( data )}
Para cada miembro, el flujo de datos avanzará hasta que encontremos el miembro que necesitamos analizar, almacenando las ubicaciones interesadas para su posterior análisis. Este proceso también nos permite analizar otras clases como miembros. json_class<"member_name", Type>
tipo de mapeo Para que cada rasgo de mapeo solo tenga que tratar con sus miembros específicos y no con sus detalles.
En contextos sin nombre, como el valor raíz, elementos de matriz, algunos tipos de valores clave y listas de elementos variantes donde el nombre sería no_name
, se pueden usar algunos tipos de datos nativos de C++ en lugar de los tipos de mapeo JSON. Esto incluye enteros, coma flotante, bool, std::string, std::string_view, contenedores asociativos, contenedores de secuencia, tipos anulables/opcionales y clases previamente asignadas.
Por ejemplo, para asignar una matriz de cadenas.
template <>
struct daw ::json::json_data_contract<MyType> {
using type = json_member_list<json_array< " member_name " , std::string>>;
};
Se puede usar vcpkg para obtener la última versión, el puerto se llama daw-json-link
find_package ( daw-json-link )
#...
target_link_libraries ( MyTarget daw::daw-json-link )
La biblioteca es solo de encabezado y se puede clonar, junto con sus dos dependencias, y luego se agregan las subcarpetas include/
de cada una a la ruta de inclusión del compilador.
Para usar daw_json_link en sus proyectos de cmake, agregar lo siguiente debería permitirle incorporarlo junto con las dependencias:
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 )
En un sistema con bash, es similar en otros sistemas también, se puede instalar lo siguiente para el sistema
git clone https://github.com/beached/daw_json_link
cd daw_json_link
mkdir build
cd build
cmake ..
cmake --install .
Esto permitirá instalar cmake find_package o usarlo como encabezado normal siempre que la carpeta de inclusión del prefijo de instalación esté incluida en las rutas de inclusión del compilador.
Lo siguiente construirá y ejecutará las pruebas.
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 .
Después de la compilación, también se pueden probar los ejemplos individuales. city_test_bin
requiere la ruta al archivo JSON de las ciudades.
./tests/city_test_bin ../test_data/cities.json
El orden de los miembros en las estructuras de datos generalmente debe coincidir con el de los datos JSON, si es posible. El analizador es más rápido si no tiene que retroceder en busca de valores. Los valores opcionales, cuando faltan en los datos JSON, también pueden ralentizar el análisis. Si es posible, envíelos como nulos. El analizador no asigna. Los tipos de datos analizados pueden y esto permite usar asignadores personalizados o una combinación, ya que sus estructuras de datos harán la asignación. El valor predeterminado para las matrices es utilizar std::vector y, si esto no es deseable, debe proporcionar el tipo.
Actualmente, la biblioteca no elimina el escape/escapa de los nombres de los miembros al serializar; se espera que sean válidos y no tengan escape. Esta puede ser una futura incorporación opcional, ya que tiene un coste.
Existen ligeras diferencias entre C++17 y C++20, donde C++20 permite parte del código que no está disponible 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>>;
};
}
Ambas versiones de C++ admiten este método para nombrar miembros.
Cuando se compila dentro del compilador C++ 20, además de pasar un char const *
como en C++ 17, los nombres de los miembros se pueden especificar directamente como literales de cadena. El soporte del compilador C++20 aún es muy temprano y aquí hay dragones. Hay problemas conocidos con g++9.x en modo C++20 y solo se prueba con g++10/11. Aquí hay dragones
namespace daw ::json {
template <>
struct json_data_contract <MyType> {
using type = json_member_list<json_number< " member_name " >>;
};
}
Una vez que un tipo de datos se ha asignado con json_data_contract
, la biblioteca proporciona métodos para analizar JSON.
MyClass my_class = from_json<MyClass>( json_str );
Alternativamente, si la entrada es confiable, la versión menos verificada puede ser más rápida
MyClass my_class = from_json<MyClass, options::parse_flags<options::CheckedParseMode::no>>( json_str );
Los documentos JSON con raíz de matriz utilizan la función from_json_array
para analizar
std::vector<MyClass> my_data = from_json_array<MyClass>( json_str );
Alternativamente, si la entrada es confiable, la versión menos verificada puede ser más rápida
std::vector<MyClass> my_data = from_json_array<MyClass, std::vector<MyClass>, options::parse_flags<options::CheckedParseMode::no>>( json_str );
json_array_iterator
Si desea trabajar a partir de datos de matriz JSON, puede obtener un iterador y usar los algoritmos estándar para iterar sobre matrices en datos JSON que se pueden realizar a través de json_array_iterator
using iterator_t = json_array_iterator<MyClass>;
auto pos = std::find( iterator_t ( json_str ), iterator_t ( ), MyClass( ... ) );
Alternativamente, si la entrada es confiable, puede llamar a la versión menos verificada
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
Para un DOM como api, que a menudo se usa para cosas como GUI y proporcionar código cuando las asignaciones son inadecuadas, se puede usar json_value
. Esto se utiliza en la herramienta json_to_cpp.
auto jv = daw::json::json_value( json_doc );
Se puede usar una ruta JSON para extraer un número entero
int foo = as< int >( jv[ " path.to.int " ] );
Aquí, "path.to.int"
es una ruta JSON que representa la navegación en una clase JSON como
{
"path" : {
"to" : {
"int" : 5
}
}
}
También se puede seleccionar mediante una sintaxis similar a una matriz en la ruta JSON, "path[5]"
seleccionaría el quinto elemento/miembro de "path"
. Si desea serializar a JSON. La sintaxis de JSON Path también funciona con from_json
, from_json_array
y json_array_iterator
.
to_json
std::string my_json_data = to_json( MyClass{} );
O serializar una matriz, colección, rango o vista de cosas. Solo requiere std::begin(...)
y std::end(...)
funcionen para el tipo. Esto permite la serialización cuando el tipo no es una colección de cosas construible.
std::vector<MyClass> arry = ...;
std::string my_json_data = to_json_array( arry );
Los errores de análisis de forma predeterminada arrojan un daw::json::json_exception
que incluye información sobre el motivo y la ubicación del error.
Si las excepciones están deshabilitadas, la biblioteca llamará std::terminate
ante un error de análisis de forma predeterminada.
Mientras que, el manejo de errores por defecto arroja un daw::json::json_exception
en los errores, o llama std::terminate
si las excepciones están deshabilitadas. Se puede cambiar este comportamiento configurando el puntero de función daw::json::daw_json_error_handler
. El único requisito es que la función no regrese. Un ejemplo que utiliza esto está en error_handling_bench_test.cpp
La comprobación de errores se puede modificar según el análisis. from_json
, from_json_array
, json_value
, json_array_iterator
y similares todos admiten opciones de análisis. A las llamadas se les puede proporcionar una opción de analizador. Las opciones disponibles están documentadas en el elemento del libro de recetas parser_policies.
daw::json::json_exception
tiene una función miembro std::string_view reason( ) const
similar a std::exception
's what( )
pero devuelve un std::string
con más contexto que what( )
. Si desea deshabilitar excepciones en un entorno que las tiene, puede definir DAW_JSON_DONT_USE_EXCEPTIONS
para deshabilitar el lanzamiento de excepciones por parte de la biblioteca o configurar el controlador; esto ya no se recomienda ya que el controlador se puede configurar en uno de los dos valores predeterminados daw::json::default_error_handling_throwing
o daw::json::default_error_handling_terminating
.
Esto se puede lograr escribiendo una especialización de json_data_contract
en el espacio de nombres daw::json
. Por ejemplo:
# 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);
}
Ver en el Explorador del compilador
Se admiten constructores agregados y de usuario. La descripción proporciona los valores necesarios para construir su tipo y el orden. El orden especificado es el orden en que se colocan en el constructor. También hay puntos de personalización para proporcionar una forma de construir su tipo. Una clase como:
# 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 " >
>;
};
}
Funciona también. Lo mismo pero 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>
>;
};
}
Las descripciones de clases son recursivas con sus submiembros. Usando el AggClass
anterior se puede incluirlo como miembro de otra clase.
// 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>
>;
};
}
Lo anterior asigna una clase MyClass
que tiene otra clase que se describe AggClass. Además, puede ver que los nombres de los miembros de la clase C++ no tienen que coincidir con los nombres JSON asignados y que las cadenas pueden usar std::string_view
como tipo de resultado. Esta es una mejora de rendimiento importante si puede garantizar que el búfer que contiene el archivo JSON existirá mientras exista la clase.
Iterando sobre matrices JSON. El iterador de entrada daw::json::json_array_iterator<JsonElement>
permite iterar sobre la matriz de elementos JSON. Técnicamente es un iterador de entrada, pero se puede almacenar y reutilizar como un iterador directo. No devuelve una referencia sino un valor.
# 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 " ;
}
}
El análisis puede comenzar en un miembro o elemento específico. Se puede especificar una ruta de miembro opcional a from_json
, from_json_array
, json_value
, json_array_iterator
y similares. El formato es una lista de nombres de miembros separados por puntos y, opcionalmente, un índice de matriz como member0.member1
que es como analizar desde:
{
"member0" : {
"member1" : {}
}
}
o member0[5].member1
que comenzaría a analizar en "miembro1" en un documento como:
{
"member0" : [
" a " ,
" b " ,
" c " ,
" d " ,
" e " ,
{
"member1" : " "
}
]
}
o
{
"member0" : {
"a" : " " ,
"b" : " " ,
"c" : " " ,
"d" : " " ,
"e" : " " ,
"f" : {
"member1" : " "
}
}
}
Los comentarios se admiten cuando se utiliza la política del analizador para ellos. Actualmente, existen dos formas de políticas de comentarios.
//
comentarios de línea y comentarios de estilo C /* */
. { // This is a comment
"a" /*this is also a comment*/: "a's value"
}
#
comentarios de línea { # This is a comment
"a" #this is also a comment
: "a's value"
}
La política de comentarios se puede configurar a través de PolicyCommentTypes
. Consulte parser_policies para obtener más información.
Para habilitar la serialización, uno debe crear una función estática adicional en su especialización de json_data_contract
llamada to_json_data( Thing const & );
que devuelve una tupla de miembros. Proporcionará una asignación de su tipo a los argumentos proporcionados en la descripción de la clase. Para serializar en una cadena JSON, se llama to_json( my_thing );
donde my_thing
es un tipo registrado o uno de los tipos fundamentales como contenedores, mapas, cadenas, bool y números. El resultado del método estático to_json_data( Thing const & )
es una tuple
cuyos elementos coinciden en orden en el type
de alias json_data_contract
adjunto. Debido a la forma en que se usa el método, las tuplas con elementos rvalue resultarán en un error de uso después de la destrucción. El compilador generará un error si esto sucede. Incluir <daw/daw_tuple_forward.h>
y el método daw::forward_nonrvalue_as_tuple
almacenará los valores en lugar de pasarlos por referencia. A menudo es el resultado de elementos de tupla calculados. Usando el ejemplo anterior, agreguemos un método 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 );
Alternativamente, se puede generar cualquier tipo de WritableOutput; de forma predeterminada, esto incluye ARCHIVO*, iostreams, contenedores de caracteres y punteros de caracteres. En json_data_constract
de su tipo. O si se opta por participar, se puede obtener un operador ostream<< para su tipo que inserta el json en el flujo de salida agregando un alias de tipo llamado opt_into_iostreams
, el tipo al que se alias no importa, e incluye daw/json/daw_json_iostream.h
. Por ejemplo
# 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 ' ;
Puede encontrar un ejemplo funcional en daw_json_iostream_test.cpp o en el explorador del compilador.
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>
Hay algunas definiciones que afectan el funcionamiento de JSON Link.
DAW_JSON_DONT_USE_EXCEPTIONS
: controla si se permiten excepciones. Si no es así, se producirá un error std::terminate()
. Esto es automático si las excepciones están deshabilitadas (por ejemplo, -fno-exceptions
).DAW_ALLOW_SSE42
: permite el modo experimental SSE42, generalmente el modo constexpr es más rápidoDAW_JSON_NO_CONST_EXPR
: esto se puede usar para permitir que clases sin mover/copiar miembros especiales se construyan a partir de datos JSON anteriores a C++ 20. Este modo no funciona en una expresión constante anterior a C++ 20 cuando este indicador ya no es necesario. Es posible que los compiladores más antiguos aún funcionen, pero al realizar pruebas, algunos dieron como resultado errores de compilación o de ICE debido a problemas de compatibilidad con C++17. A menudo, no utilizar constexpr también puede ayudar.
json_key_value
.std::multimap<std::string, T>
o std::vector<std::pair<std::string, T>>
todos los miembros se conservan con el primero en orden. Alternativamente, el tipo json_value
permitirá la iteración sobre los miembros de la clase y el análisis diferido del correcto. Consulte Valores clave del libro de cocina que demuestra estos métodos.