
A biblioteca JSON Link é uma biblioteca C++ JSON de alto desempenho e sem alocação que oferece suporte a:
A biblioteca suporta outros modos de análise que também podem ser misturados.
json_array_iterator ou json_array_range .json_value que permite iteração/análise lenta do documentoAlguns outros recursos notáveis são:
boost::multiprecision::cpp_int ou GNU BigNum/Rational mpq_tA biblioteca está usando a licença BSL
Quando a estrutura do documento JSON é conhecida, a análise é semelhante a esta:
MyThing thing = daw::json::from_json<MyThing>( json_string );ou para documentos de array, onde a raiz do documento é um array, existe um método auxiliar para facilitar e pode ser analisado da seguinte forma:
std::vector<MyThing> things = daw::json::from_json_array<MyThing>( json_string2 ); Se a estrutura do documento JSON for desconhecida, pode-se construir um json_value que atue como uma visualização e permita iteração e análise pull sob demanda. A seguir está um exemplo de abertura de um json_value a partir de dados JSON:
json_value val = daw::json::json_value( json_string ); Os métodos from_json e to_json permitem acessar a maioria das necessidades de análise e serialização.
O analisador baseado em eventos (SAX) pode ser chamado via daw::json::json_event_parser . São necessários dois argumentos, um documento JSON e um manipulador de eventos. O manipulador de eventos pode aceitar eventos tendo qualquer um dos seguintes membros:
O mapeamento de suas classes para documentos JSON é feito especializando a característica daw::json::json_data_contract . Uma classe mapeada não precisa ser mapeada novamente se for membro de outra classe mapeada. Existem duas partes na característica json_data_contract , a primeira é um alias de tipo chamado type que mapeia os membros JSON para o construtor de nossa classe. Isso evita a necessidade de acesso privado à classe, assumindo que os dados que serializaríamos também seriam necessários para construir a classe. Por exemplo:
struct Thing {
int a;
int b;
}; A construção para Thing requer 2 números inteiros e se tivéssemos o seguinte JSON:
{
"a" : 42 ,
"b" : 1234
}Poderíamos fazer o mapeamento da seguinte forma:
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 >
>;
};
} Isso diz que a classe JSON, no documento, terá pelo menos dois membros "a" e "b" que serão números inteiros. Eles serão passados para o construtor de Thing quando daw::json::from_json<Thing>( json_doc ); é chamado ou que outra classe tem um mapeamento de membro json_class<MemberName, Thing> . O acima é o método de mapeamento C++ 17 para os nomes, ele também funciona em versões futuras do C++. Mas, em C++20 e posterior, os nomes podem estar embutidos no mapeamento, por exemplo, json_number<"a", int> . O texto acima é tudo o que é necessário para analisar JSON, para serializar uma função de membro estático é necessária na característica. Pegando o exemplo anterior e estendendo-o, poderíamos serializar Thing com:
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 );
}
} A ordem dos membros retornados como uma tupla precisa corresponder ao mapeamento no tipo alias type . Isso também permite passar o resultado dos métodos acessadores, se os membros dos dados não forem públicos. Além disso, a classe Thing deve ser construtível a partir de int, int . A biblioteca oferece suporte a construtores regulares e init agregado ( Thing{ int, int } e Thing( int, int ) ) em C++17.
to_json_data não precisa retornar uma tupla de referências aos membros do objeto existentes, mas também pode retornar valores calculados. Ele não permite a passagem de rvalues, pois geralmente são temporários e pode resultar em depuração de longa distância. A biblioteca irá static_assert sobre isso e sugerirá incluir <daw/daw_tuple_forward.h> e chamar daw::forward_nonrvalue_as_tuple( ... ) que armazenam temporários e encaminham outros tipos de referência. Os analisadores funcionam construindo cada argumento na chamada ao construtor da classe. Os analisadores de argumentos individuais podem ser ajustados para as circunstâncias especificadas dos dados (por exemplo, ponto flutuante e números inteiros). Então, com nossa característica de tipo definindo os argumentos necessários para construir a classe C++ e sua ordem, podemos observar cada membro no JSON. Agora construímos o valor com o resultado de cada analisador; semelhante a T{ parse<0, json_string<"name">>( data ), parse<1, json_number<"age", unsigned>>( data ), parse<json_number<2, "number>>( data )} . Para cada membro, o fluxo de dados avançará até encontrarmos o membro que precisamos analisar, armazenando os locais interessados para análise posterior. Esse processo também nos permite analisar outras classes como membros. json_class<"member_name", Type> tipo de mapeamento Para que cada característica de mapeamento tenha que lidar apenas com seus membros específicos e não com seus detalhes. 
Em contextos sem nome, como valor raiz, elementos de matriz, alguns tipos de valores-chave e listas de elementos variantes onde o nome seria no_name , pode-se usar alguns tipos de dados C++ nativos em vez dos tipos de mapeamento JSON. Isso inclui inteiro, ponto flutuante, bool, std::string, std::string_view, contêineres associativos, contêineres de sequência, tipos anuláveis/opcionais e classes mapeadas anteriormente.
Por exemplo, para mapear uma matriz de strings.
template <>
struct daw ::json::json_data_contract<MyType> {
using type = json_member_list<json_array< " member_name " , std::string>>;
}; Pode-se usar vcpkg para obter a versão mais recente, a porta é chamada daw-json-link
find_package ( daw-json-link )
#...
target_link_libraries ( MyTarget daw::daw-json-link ) A biblioteca é apenas de cabeçalho e pode ser clonada, juntamente com suas duas dependências, seguida pela adição das subpastas include/ de cada uma ao caminho de inclusão do compilador
Para usar daw_json_link em seus projetos cmake, adicionar o seguinte deve permitir que ele seja extraído junto com as dependências:
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 )Em um sistema com bash, é semelhante em outros sistemas também, o seguinte pode ser instalado para o sistema
git clone https://github.com/beached/daw_json_link
cd daw_json_link
mkdir build
cd build
cmake ..
cmake --install . Isso permitirá a instalação do cmake find_package ou seu uso como um cabeçalho regular, desde que a pasta de inclusão do prefixo de instalação esteja incluída nos caminhos de inclusão do compilador
O seguinte irá construir e executar os testes.
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 . Após a construção, os exemplos individuais também podem ser testados. city_test_bin requer o caminho para o arquivo JSON das cidades.
./tests/city_test_bin ../test_data/cities.jsonA ordem dos membros nas estruturas de dados geralmente deve corresponder à dos dados JSON, se possível. O analisador é mais rápido se não precisar rastrear valores. Valores opcionais, quando ausentes nos dados JSON, também podem retardar a análise. Se possível, envie-os como nulos. O analisador não aloca. Os tipos de dados analisados podem e isso permite usar alocadores personalizados ou uma combinação, pois suas estruturas de dados farão a alocação. O padrão para arrays é usar std::vector e se isso não for desejável, você deve fornecer o tipo.

A biblioteca, atualmente, não remove/escapa de nomes de membros durante a serialização, espera-se que eles sejam válidos e sem escape. Esta pode ser uma adição opcional futura, pois tem um custo.
Existem pequenas diferenças entre C++17 e C++20, onde C++20 permite alguns códigos não disponíveis em 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 as versões do C++ suportam este método para nomear membros.
Quando compilado no compilador C++20, além de passar um char const * como em C++17, os nomes dos membros podem ser especificados diretamente como literais de string. O suporte ao compilador C++ 20 ainda é muito cedo e aqui estão os dragões. Existem problemas conhecidos com g++9.x no modo C++20 e ele só foi testado com g++10/11. Aqui estão dragões
namespace daw ::json {
template <>
struct json_data_contract <MyType> {
using type = json_member_list<json_number< " member_name " >>;
};
} Depois que um tipo de dados for mapeado com json_data_contract , a biblioteca fornece métodos para analisar JSON para eles
MyClass my_class = from_json<MyClass>( json_str );Alternativamente, se a entrada for confiável, a versão menos verificada poderá ser mais rápida
MyClass my_class = from_json<MyClass, options::parse_flags<options::CheckedParseMode::no>>( json_str ); Documentos JSON com raiz de array usam a função from_json_array para analisar
std::vector<MyClass> my_data = from_json_array<MyClass>( json_str );Alternativamente, se a entrada for confiável, a versão menos verificada poderá ser mais 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 Se você quiser trabalhar a partir de dados de array JSON, você pode obter um iterador e usar os algoritmos std para iterar sobre arrays em dados JSON pode ser feito através do json_array_iterator
using iterator_t = json_array_iterator<MyClass>;
auto pos = std::find( iterator_t ( json_str ), iterator_t ( ), MyClass( ... ) );Alternativamente, se a entrada for confiável, você pode chamar a versão 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 um DOM como API, frequentemente usado para coisas como GUIs e fornecimento de código quando os mapeamentos são inadequados, pode-se usar json_value . Isso é usado na ferramenta json_to_cpp.
auto jv = daw::json::json_value( json_doc );Pode-se usar um caminho JSON para extrair um número inteiro
int foo = as< int >( jv[ " path.to.int " ] ); Aqui, "path.to.int" é um caminho JSON que representa o detalhamento de uma classe JSON como
{
"path" : {
"to" : {
"int" : 5
}
}
} Também é possível selecionar por meio de uma sintaxe semelhante a um array no caminho JSON, "path[5]" selecionaria o 5º elemento/membro de "path" . Se você deseja serializar para JSON. A sintaxe JSON Path funciona com from_json , from_json_array e json_array_iterator também.
to_jsonstd::string my_json_data = to_json( MyClass{} ); Ou serialize uma matriz, coleção, intervalo ou visualização de coisas. Requer apenas std::begin(...) e std::end(...) para funcionar para o tipo. Isso permite a serialização quando o tipo não é uma coleção construtível de coisas.
std::vector<MyClass> arry = ...;
std::string my_json_data = to_json_array( arry ); Os erros de análise são padronizados para lançar um daw::json::json_exception que inclui informações sobre o motivo e o local da falha.
Se as exceções estiverem desativadas, a biblioteca chamará std::terminate após um erro de análise por padrão.
Embora o padrão de tratamento de erros seja lançar um daw::json::json_exception em caso de erros ou chamar std::terminate se as exceções estiverem desativadas. Pode-se alterar esse comportamento definindo o ponteiro de função daw::json::daw_json_error_handler . O único requisito é que a função não retorne. Um exemplo que utiliza isso está em error_handling_bench_test.cpp
A verificação de erros pode ser modificada por análise. from_json , from_json_array , json_value , json_array_iterator e todos suportam opções de análise. as chamadas podem receber uma opção de analisador. As opções disponíveis estão documentadas no item do livro de receitas parser_policies.
daw::json::json_exception tem uma função de membro std::string_view reason( ) const semelhante a std::exception é what( ) mas retorna um std::string com mais contexto do que what( ) faz. Se você deseja desabilitar exceções em um ambiente que as possui, você pode definir DAW_JSON_DONT_USE_EXCEPTIONS para desabilitar o lançamento de exceções pela biblioteca ou definir o manipulador, isso não é mais recomendado, pois o manipulador pode ser definido como um dos dois padrões daw::json::default_error_handling_throwing ou daw::json::default_error_handling_terminating .
Isso pode ser feito escrevendo uma especialização de json_data_contract no namespace daw::json . Por exemplo:
# 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);
}Veja no Compiler Explorer
Há suporte para construtores agregados e de usuário. A descrição fornece os valores necessários para construir seu tipo e a ordem. A ordem especificada é a ordem em que são colocados no construtor. Existem pontos de personalização para fornecer uma maneira de construir seu tipo também. Uma aula 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 também. O mesmo, mas 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>
>;
};
} As descrições das classes são recursivas com seus submembros. Usando o AggClass anterior pode-se incluí-lo como membro de outra 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>
>;
};
} O acima mapeia uma classe MyClass que possui outra classe descrita como AggClass. Além disso, você pode ver que os nomes dos membros da classe C++ não precisam corresponder aos nomes JSON mapeados e que as strings podem usar std::string_view como o tipo de resultado. Este é um aprimoramento de desempenho importante se você puder garantir que o buffer que contém o arquivo JSON existirá enquanto a classe existir.
Iterando sobre matrizes JSON. O iterador de entrada daw::json::json_array_iterator<JsonElement> permite iterar sobre a matriz de elementos JSON. É tecnicamente um iterador de entrada, mas pode ser armazenado e reutilizado como um iterador direto. Ele não retorna uma referência, mas um 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 " ;
}
} A análise pode começar em um membro ou elemento específico. Um caminho de membro opcional para from_json , from_json_array , json_value , json_array_iterator e similares pode ser especificado. O formato é uma lista separada por pontos de nomes de membros e, opcionalmente, um índice de matriz como member0.member1 que é como analisar de:
{
"member0" : {
"member1" : {}
}
} ou member0[5].member1 que começaria a análise em "member1" em um documento como:
{
"member0" : [
" a " ,
" b " ,
" c " ,
" d " ,
" e " ,
{
"member1" : " "
}
]
}ou
{
"member0" : {
"a" : " " ,
"b" : " " ,
"c" : " " ,
"d" : " " ,
"e" : " " ,
"f" : {
"member1" : " "
}
}
}Os comentários são suportados quando a política do analisador para eles é usada. Atualmente, existem duas formas de políticas de comentários.
// comentários de linha e estilo C /* */ comentários. { // This is a comment
"a" /*this is also a comment*/: "a's value"
}
# comentários de linha { # This is a comment
"a" #this is also a comment
: "a's value"
}
A política de comentários pode ser definida por meio de PolicyCommentTypes . Consulte parser_policies para obter mais informações.
Para habilitar a serialização, é necessário criar uma função estática adicional em sua especialização de json_data_contract chamada to_json_data( Thing const & ); que retorna uma tupla de membros. Ele fornecerá um mapeamento do seu tipo para os argumentos fornecidos na descrição da classe. Para serializar para uma string JSON, chama-se to_json( my_thing ); onde my_thing é um tipo registrado ou um dos tipos fundamentais como Containers, Maps, Strings, bool e numbers. O resultado do método estático to_json_data( Thing const & ) é uma tuple cujos elementos correspondem à ordem no alias type do tipo json_data_contract que o acompanha. Devido à forma como o método é usado, tuplas com elementos rvalue resultarão em um bug de uso após destruição. O compilador apresentará um erro se isso acontecer. Incluir <daw/daw_tuple_forward.h> e o método daw::forward_nonrvalue_as_tuple armazenará os rvalues em vez de passá-los por referência. Freqüentemente, é o resultado de elementos de tupla calculados. Usando o exemplo acima, vamos adicionar um 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, pode-se gerar saída para qualquer tipo WritableOutput, por padrão isso inclui FILE*, iostreams, contêineres de caracteres e ponteiros de caracteres. No json_data_constract do seu tipo. Ou, se optado, pode-se obter um operador ostream<< para seu tipo que insere o json no fluxo de saída adicionando um alias de tipo chamado opt_into_iostreams o tipo que o alias não importa e inclui daw/json/daw_json_iostream.h . Por exemplo
# 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 ' ;Um exemplo prático pode ser encontrado em daw_json_iostream_test.cpp ou no compilador Explorer
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>Existem algumas definições que afetam o funcionamento do JSON Link
DAW_JSON_DONT_USE_EXCEPTIONS – Controla se exceções são permitidas. Se não estiverem, ocorrerá um std::terminate() em caso de erros. Isso é automático se as exceções estiverem desativadas (por exemplo, -fno-exceptions )DAW_ALLOW_SSE42 - Permitir modo experimental SSE42, geralmente o modo constexpr é mais rápidoDAW_JSON_NO_CONST_EXPR - Isso pode ser usado para permitir que classes sem mover/copiar membros especiais sejam construídas a partir de dados JSON anteriores ao C++ 20. Este modo não funciona em uma expressão constante anterior ao C++ 20 quando esse sinalizador não é mais necessário. Compiladores mais antigos ainda podem funcionar, mas em testes alguns resultaram em ICEs ou erros de compilação devido ao suporte com bugs do C++ 17. Freqüentemente, não usar o constexpr também pode ajudar.
json_key_value .std::multimap<std::string, T> ou std::vector<std::pair<std::string, T>> todos os membros são preservados com o primeiro em ordem. Alternativamente, o tipo json_value permitirá a iteração sobre os membros da classe e a análise lenta do correto. Consulte Valores-chave do livro de receitas que demonstra esses métodos.