json
a partir de literais JSONExistem inúmeras bibliotecas JSON por aí, e cada uma pode até ter sua razão de existir. Nossa classe tinha estes objetivos de design:
Sintaxe intuitiva . Em linguagens como Python, JSON parece um tipo de dados de primeira classe. Usamos toda a magia do operador do C++ moderno para obter a mesma sensação em seu código. Confira os exemplos abaixo e você entenderá o que quero dizer.
Integração trivial . Todo o nosso código consiste em um único arquivo de cabeçalho json.hpp
. É isso. Sem biblioteca, sem subprojeto, sem dependências, sem sistema de construção complexo. A classe é escrita em vanilla C++11. Resumindo, tudo não deve exigir nenhum ajuste nos sinalizadores do compilador ou nas configurações do projeto.
Testes sérios . Nosso código é fortemente testado em unidade e cobre 100% do código, incluindo todo comportamento excepcional. Além disso, verificamos com Valgrind e Clang Sanitizers se não há vazamentos de memória. O Google OSS-Fuzz também executa testes fuzz em todos os analisadores 24 horas por dia, 7 dias por semana, executando efetivamente bilhões de testes até o momento. Para manter a alta qualidade, o projeto segue as melhores práticas da Core Infrastructure Initiative (CII).
Outros aspectos não foram tão importantes para nós:
Eficiência de memória . Cada objeto JSON possui uma sobrecarga de um ponteiro (o tamanho máximo de uma união) e um elemento de enumeração (1 byte). A generalização padrão usa os seguintes tipos de dados C++: std::string
para strings, int64_t
, uint64_t
ou double
para números, std::map
para objetos, std::vector
para matrizes e bool
para booleanos. No entanto, você pode modelar a classe generalizada basic_json
de acordo com suas necessidades.
Velocidade . Certamente existem bibliotecas JSON mais rápidas por aí. No entanto, se o seu objetivo é acelerar o seu desenvolvimento adicionando suporte JSON com um único cabeçalho, então esta biblioteca é o caminho a percorrer. Se você sabe como usar um std::vector
ou std::map
, você já está pronto.
Consulte as diretrizes de contribuição para obter mais informações.
Você pode patrocinar esta biblioteca em GitHub Sponsors.
Obrigado a todos!
❓ Se você tiver alguma dúvida , verifique se ela já foi respondida nas perguntas frequentes ou na seção de perguntas e respostas . Caso contrário, faça uma nova pergunta lá.
Se você quiser saber mais sobre como usar a biblioteca, confira o restante do README , dê uma olhada nos exemplos de código ou navegue pelas páginas de ajuda .
? Se você quiser entender melhor a API , confira a Referência da API .
? Se você encontrou um bug , verifique o FAQ se for um problema conhecido ou resultado de uma decisão de design. Dê também uma olhada na lista de problemas antes de criar um novo problema . Forneça o máximo de informações possível para nos ajudar a entender e reproduzir seu problema.
Há também um conjunto de documentos para a documentação dos navegadores Dash, Velocity e Zeal que contém a documentação completa como recurso offline.
Aqui estão alguns exemplos para lhe dar uma ideia de como usar a classe.
Além dos exemplos abaixo, você pode querer:
→ Verifique a documentação
→ Navegue pelos arquivos de exemplo independentes
Cada função da API (documentada na documentação da API) possui um arquivo de exemplo independente correspondente. Por exemplo, a função emplace()
possui um arquivo de exemplo emplace.cpp correspondente.
A classe json
fornece uma API para manipular um valor JSON. Para criar um objeto json
lendo um arquivo JSON:
# include < fstream >
# include < nlohmann/json.hpp >
using json = nlohmann::json;
// ...
std::ifstream f ( " example.json " );
json data = json::parse(f);
json
a partir de literais JSON Suponha que você queira criar esse valor JSON literal em código em um arquivo, como um objeto json
:
{
"pi" : 3.141 ,
"happy" : true
}
Existem várias opções:
// 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 },
};
Aqui estão alguns exemplos para lhe dar uma ideia de como usar a classe.
Suponha que você queira criar o objeto JSON
{
"pi" : 3.141 ,
"happy" : true ,
"name" : " Niels " ,
"nothing" : null ,
"answer" : {
"everything" : 42
},
"list" : [ 1 , 0 , 2 ],
"object" : {
"currency" : " USD " ,
"value" : 42.99
}
}
Com esta biblioteca, você poderia escrever:
// 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 }
}}
};
Observe que em todos esses casos, você nunca precisa "informar" ao compilador qual tipo de valor JSON deseja usar. Se você quiser ser explícito ou expressar alguns casos extremos, as funções json::array()
e json::object()
ajudarão:
// 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 } });
Você pode criar um valor JSON (desserialização) anexando _json
a uma string literal:
// 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;
Observe que sem anexar o sufixo _json
, a string literal passada não é analisada, mas apenas usada como valor da string JSON. Ou seja, json j = "{ "happy": true, "pi": 3.141 }"
apenas armazenaria a string "{ "happy": true, "pi": 3.141 }"
em vez de analisar o valor real objeto.
A string literal deve ser trazida para o escopo using namespace nlohmann::literals;
(veja json::parse()
).
O exemplo acima também pode ser expresso explicitamente usando json::parse()
:
// parse explicitly
auto j3 = json::parse( R"( {"happy": true, "pi": 3.141} )" );
Você também pode obter uma representação em string de um valor JSON (serializar):
// 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
// }
Observe a diferença entre serialização e atribuição:
// 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()
retorna o valor da string originalmente armazenado.
Observe que a biblioteca oferece suporte apenas a UTF-8. Quando você armazena strings com codificações diferentes na biblioteca, chamar dump()
pode gerar uma exceção, a menos que json::error_handler_t::replace
ou json::error_handler_t::ignore
sejam usados como manipuladores de erros.
Você também pode usar streams para serializar e desserializar:
// 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;
Esses operadores funcionam para qualquer subclasse de std::istream
ou std::ostream
. Aqui está o mesmo exemplo com arquivos:
// 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;
Observe que definir o bit de exceção para failbit
é inadequado para este caso de uso. Isso resultará no encerramento do programa devido ao especificador noexcept
em uso.
Você também pode analisar JSON de um intervalo de iterador; isto é, de qualquer contêiner acessível por iteradores cujo value_type
seja um tipo integral de 1, 2 ou 4 bytes, que será interpretado como UTF-8, UTF-16 e UTF-32 respectivamente. Por exemplo, um std::vector<std::uint8_t>
ou um std::list<std::uint16_t>
:
std::vector<std:: uint8_t > v = { ' t ' , ' r ' , ' u ' , ' e ' };
json j = json::parse(v.begin(), v.end());
Você pode deixar os iteradores para o intervalo [begin, end):
std::vector<std:: uint8_t > v = { ' t ' , ' r ' , ' u ' , ' e ' };
json j = json::parse(v);
Como a função de análise aceita intervalos de iteradores arbitrários, você pode fornecer suas próprias fontes de dados implementando o conceito 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);
}
A biblioteca usa uma interface semelhante a SAX com as seguintes funções:
// 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);
O valor de retorno de cada função determina se a análise deve prosseguir.
Para implementar seu próprio manipulador SAX, proceda da seguinte forma:
nlohmann::json_sax<json>
como classe base, mas também pode usar qualquer classe onde as funções descritas acima sejam implementadas e públicas.my_sax
.bool json::sax_parse(input, &my_sax)
; onde o primeiro parâmetro pode ser qualquer entrada como uma string ou fluxo de entrada e o segundo parâmetro é um ponteiro para sua interface SAX. Observe que a função sax_parse
retorna apenas um bool
indicando o resultado do último evento SAX executado. Ele não retorna um valor json
– cabe a você decidir o que fazer com os eventos SAX. Além disso, nenhuma exceção é lançada no caso de um erro de análise - cabe a você decidir o que fazer com o objeto de exceção passado para sua implementação parse_error
. Internamente, a interface SAX é usada para o analisador DOM (classe json_sax_dom_parser
), bem como para o aceitador ( json_sax_acceptor
), consulte o arquivo json_sax.hpp
.
Projetamos a classe JSON para se comportar como um contêiner STL. Na verdade, ele atende ao requisito 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 " );
Qualquer contêiner de sequência ( std::array
, std::vector
, std::deque
, std::forward_list
, std::list
) cujos valores podem ser usados para construir valores JSON (por exemplo, inteiros, números de ponto flutuante, booleanos, string tipos ou novamente contêineres STL descritos nesta seção) podem ser usados para criar uma matriz JSON. O mesmo vale para contêineres associativos semelhantes ( std::set
, std::multiset
, std::unordered_set
, std::unordered_multiset
), mas nesses casos a ordem dos elementos da matriz depende de como os elementos são ordenados no respectivo contêiner STL.
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"]
Da mesma forma, quaisquer contêineres de valor-chave associativos ( std::map
, std::multimap
, std::unordered_map
, std::unordered_multimap
) cujas chaves podem construir um std::string
e cujos valores podem ser usados para construir valores JSON (consulte exemplos acima) podem ser usados para criar um objeto JSON. Observe que no caso de multimapas apenas uma chave é usada no objeto JSON e o valor depende da ordem interna do contêiner 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}
A biblioteca suporta JSON Pointer (RFC 6901) como meio alternativo para endereçar valores estruturados. Além disso, JSON Patch (RFC 6902) permite descrever diferenças entre dois valores JSON - permitindo efetivamente operações de patch e diff conhecidas do 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" }
// ]
A biblioteca oferece suporte a JSON Merge Patch (RFC 7386) como formato de patch. Em vez de usar JSON Pointer (veja acima) para especificar valores a serem manipulados, ele descreve as alterações usando uma sintaxe que imita de perto o documento que está sendo modificado.
// 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"
// }
// }
Os tipos suportados podem ser convertidos implicitamente em valores JSON.
É recomendado NÃO USAR conversões implícitas de um valor JSON. Você pode encontrar mais detalhes sobre esta recomendação aqui. Você pode desativar as conversões implícitas definindo JSON_USE_IMPLICIT_CONVERSIONS
como 0
antes de incluir o cabeçalho json.hpp
. Ao usar o CMake, você também pode conseguir isso definindo a opção JSON_ImplicitConversions
como 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