json
객체 생성세상에는 수많은 JSON 라이브러리가 있으며 각 라이브러리마다 존재 이유가 있을 수도 있습니다. 우리 수업에는 다음과 같은 디자인 목표가 있었습니다.
직관적인 구문 . Python과 같은 언어에서 JSON은 일류 데이터 유형처럼 느껴집니다. 우리는 코드에서 동일한 느낌을 얻기 위해 최신 C++의 모든 연산자 마법을 사용했습니다. 아래 예를 확인하면 무슨 뜻인지 알게 될 것입니다.
사소한 통합 . 전체 코드는 단일 헤더 파일 json.hpp
로 구성됩니다. 그게 다야. 라이브러리도, 하위 프로젝트도, 종속성도, 복잡한 빌드 시스템도 없습니다. 클래스는 바닐라 C++11로 작성되었습니다. 대체로 컴파일러 플래그나 프로젝트 설정을 조정할 필요가 없어야 합니다.
심각한 테스트 . 우리 코드는 철저한 단위 테스트를 거쳤으며 모든 예외적인 동작을 포함하여 코드의 100%를 다룹니다. 또한 Valgrind 및 Clang Sanitizers를 통해 메모리 누수가 없는지 확인했습니다. Google OSS-Fuzz는 모든 파서에 대해 연중무휴 퍼지 테스트를 추가로 실행하여 지금까지 수십억 개의 테스트를 효과적으로 실행합니다. 높은 품질을 유지하기 위해 이 프로젝트는 CII(핵심 인프라 이니셔티브) 모범 사례를 따르고 있습니다.
다른 측면은 우리에게 그다지 중요하지 않았습니다.
메모리 효율성 . 각 JSON 객체에는 포인터 1개(공용체의 최대 크기)와 열거형 요소 1개(1바이트)의 오버헤드가 있습니다. 기본 일반화에서는 문자열의 경우 std::string
, 숫자의 경우 int64_t
, uint64_t
또는 double
, 개체의 경우 std::map
, 배열의 경우 std::vector
, 부울의 경우 bool
등 C++ 데이터 형식을 사용합니다. 그러나 필요에 따라 일반화된 클래스 basic_json
템플릿화할 수 있습니다.
속도 . 확실히 더 빠른 JSON 라이브러리가 있습니다. 그러나 단일 헤더로 JSON 지원을 추가하여 개발 속도를 높이는 것이 목표라면 이 라이브러리를 사용하는 것이 좋습니다. std::vector
또는 std::map
사용 방법을 알고 있다면 이미 설정된 것입니다.
자세한 내용은 기여 지침을 참조하세요.
GitHub 후원자에서 이 라이브러리를 후원할 수 있습니다.
모두 감사합니다!
❓ 궁금 하신 점은 FAQ 나 Q&A 에서 이미 답변되어 있는지 확인해주세요. 그렇지 않은 경우 거기에서 새로운 질문을 하십시오 .
라이브러리 사용 방법에 대해 자세히 알아 보려면 README 의 나머지 부분을 확인하거나 코드 예제를 보거나 도움말 페이지를 찾아보세요.
? API를 더 잘 이해하려면 API 참조 를 확인하세요.
? 버그를 발견한 경우, 그것이 알려진 문제인지 또는 디자인 결정의 결과인지 FAQ를 확인하세요. 새 이슈를 생성하기 전에 이슈 목록 도 살펴보시기 바랍니다. 문제를 이해하고 재현하는 데 도움이 되도록 최대한 많은 정보를 제공해 주세요.
전체 문서를 오프라인 리소스로 포함하는 문서 브라우저 Dash, Velocity 및 Zeal에 대한 문서 세트 도 있습니다.
다음은 클래스 사용 방법에 대한 아이디어를 제공하는 몇 가지 예입니다.
아래 예 외에도 다음을 수행할 수 있습니다.
→ 문서를 확인하세요
→ 독립 실행형 예제 파일 찾아보기
모든 API 함수(API 설명서에 설명되어 있음)에는 해당하는 독립 실행형 예제 파일이 있습니다. 예를 들어 emplace()
함수에는 일치하는 emplace.cpp 예제 파일이 있습니다.
json
클래스는 JSON 값을 조작하기 위한 API를 제공합니다. JSON 파일을 읽어서 json
객체를 생성하려면:
# include < fstream >
# include < nlohmann/json.hpp >
using json = nlohmann::json;
// ...
std::ifstream f ( " example.json " );
json data = json::parse(f);
json
객체 생성 이 리터럴 JSON 값을 파일에 json
객체로 하드 코딩한다고 가정합니다.
{
"pi" : 3.141 ,
"happy" : true
}
다양한 옵션이 있습니다:
// 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 },
};
다음은 클래스 사용 방법에 대한 아이디어를 제공하는 몇 가지 예입니다.
JSON 객체를 생성한다고 가정합니다.
{
"pi" : 3.141 ,
"happy" : true ,
"name" : " Niels " ,
"nothing" : null ,
"answer" : {
"everything" : 42
},
"list" : [ 1 , 0 , 2 ],
"object" : {
"currency" : " USD " ,
"value" : 42.99
}
}
이 라이브러리를 사용하면 다음과 같이 작성할 수 있습니다.
// 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 }
}}
};
이러한 모든 경우에 어떤 JSON 값 유형을 사용할지 컴파일러에 "알려줄" 필요가 없습니다. 명시적이거나 일부 극단적인 경우를 표현하려는 경우 json::array()
및 json::object()
함수가 도움이 될 것입니다.
// 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 } });
문자열 리터럴에 _json
추가하여 JSON 값(역직렬화)을 생성할 수 있습니다.
// 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;
_json
접미사를 추가하지 않으면 전달된 문자열 리터럴이 구문 분석되지 않고 JSON 문자열 값으로 사용됩니다. 즉, json j = "{ "happy": true, "pi": 3.141 }"
실제 문자열을 구문 분석하는 대신 "{ "happy": true, "pi": 3.141 }"
문자열만 저장합니다. 물체.
문자열 리터럴은 using namespace nlohmann::literals;
( json::parse()
참조).
위의 예는 json::parse()
사용하여 명시적으로 표현할 수도 있습니다.
// parse explicitly
auto j3 = json::parse( R"( {"happy": true, "pi": 3.141} )" );
JSON 값의 문자열 표현을 얻을 수도 있습니다(직렬화).
// 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
// }
직렬화와 할당의 차이점에 유의하세요.
// 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()
원래 저장된 문자열 값을 반환합니다.
라이브러리는 UTF-8만 지원합니다. 라이브러리에 다른 인코딩으로 문자열을 저장할 때 json::error_handler_t::replace
또는 json::error_handler_t::ignore
오류 처리기로 사용되지 않는 한 dump()
를 호출하면 예외가 발생할 수 있습니다.
스트림을 사용하여 직렬화 및 역직렬화할 수도 있습니다.
// 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;
이러한 연산자는 std::istream
또는 std::ostream
의 모든 하위 클래스에 대해 작동합니다. 다음은 파일과 동일한 예입니다.
// 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;
failbit
에 대한 예외 비트를 설정하는 것은 이 사용 사례에 적합하지 않습니다. noexcept
지정자를 사용하면 프로그램이 종료됩니다.
반복자 범위에서 JSON을 구문 분석할 수도 있습니다. 즉, value_type
각각 UTF-8, UTF-16 및 UTF-32로 해석되는 1, 2 또는 4바이트의 정수 유형인 반복자가 액세스할 수 있는 모든 컨테이너에서 가져옵니다. 예를 들어 std::vector<std::uint8_t>
또는 std::list<std::uint16_t>
는 다음과 같습니다.
std::vector<std:: uint8_t > v = { ' t ' , ' r ' , ' u ' , ' e ' };
json j = json::parse(v.begin(), v.end());
[시작, 끝) 범위에 대한 반복자를 그대로 둘 수 있습니다.
std::vector<std:: uint8_t > v = { ' t ' , ' r ' , ' u ' , ' e ' };
json j = json::parse(v);
구문 분석 함수는 임의의 반복자 범위를 허용하므로 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);
}
라이브러리는 다음 기능과 함께 SAX와 유사한 인터페이스를 사용합니다.
// 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);
각 함수의 반환 값에 따라 구문 분석을 계속할지 여부가 결정됩니다.
자체 SAX 핸들러를 구현하려면 다음을 수행하십시오.
nlohmann::json_sax<json>
클래스를 기본 클래스로 사용할 수 있지만 위에서 설명한 함수가 구현되고 공개된 모든 클래스를 사용할 수도 있습니다.my_sax
를 만듭니다.bool json::sax_parse(input, &my_sax)
호출합니다. 여기서 첫 번째 매개변수는 문자열이나 입력 스트림과 같은 입력이 될 수 있고 두 번째 매개변수는 SAX 인터페이스에 대한 포인터입니다. sax_parse
함수는 마지막으로 실행된 SAX 이벤트의 결과를 나타내는 bool
만 반환합니다. json
값을 반환하지 않습니다. SAX 이벤트로 무엇을 할지 결정하는 것은 사용자의 몫입니다. 게다가, 구문 분석 오류가 발생하는 경우 예외가 발생하지 않습니다. parse_error
구현에 전달된 예외 개체를 어떻게 처리할지는 사용자에게 달려 있습니다. 내부적으로 SAX 인터페이스는 DOM 구문 분석기(클래스 json_sax_dom_parser
)와 수락자( json_sax_acceptor
)에 사용됩니다. json_sax.hpp
파일을 참조하세요.
우리는 JSON 클래스가 STL 컨테이너처럼 작동하도록 설계했습니다. 실제로 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 " );
JSON 값(예: 정수, 부동 소수점 숫자, 부울, 문자열)을 구성하는 데 값을 사용할 수 있는 모든 시퀀스 컨테이너( std::array
, std::vector
, std::deque
, std::forward_list
, std::list
) 유형 또는 이 섹션에 설명된 STL 컨테이너)을 사용하여 JSON 배열을 생성할 수 있습니다. 유사한 연관 컨테이너( std::set
, std::multiset
, std::unordered_set
, std::unordered_multiset
)에도 동일하게 적용되지만 이러한 경우 배열 요소의 순서는 요소가 배열에서 어떻게 정렬되는지에 따라 달라집니다. 해당 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"]
마찬가지로, 키가 std::string
을 구성할 수 있고 해당 값이 JSON 값을 구성하는 데 사용될 수 있는 모든 연관 키-값 컨테이너( std::map
, std::multimap
, std::unordered_map
, std::unordered_multimap
) 위의 예)을 사용하여 JSON 개체를 만들 수 있습니다. 멀티맵의 경우 JSON 객체에는 하나의 키만 사용되며 값은 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}
라이브러리는 구조화된 값을 처리하는 대체 수단으로 JSON 포인터 (RFC 6901)를 지원합니다. 또한 JSON 패치 (RFC 6902)를 사용하면 두 JSON 값 간의 차이점을 설명할 수 있어 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" }
// ]
라이브러리는 JSON Merge Patch (RFC 7386)를 패치 형식으로 지원합니다. 조작할 값을 지정하기 위해 JSON 포인터(위 참조)를 사용하는 대신 수정 중인 문서와 매우 유사한 구문을 사용하여 변경 사항을 설명합니다.
// 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"
// }
// }
지원되는 유형은 암시적으로 JSON 값으로 변환될 수 있습니다.
JSON 값 에서 암시적 변환을 사용하지 않는 것이 좋습니다. 이 권장 사항에 대한 자세한 내용은 여기에서 확인할 수 있습니다. json.hpp
헤더를 포함하기 전에 JSON_USE_IMPLICIT_CONVERSIONS
0
으로 정의하여 암시적 변환을 끌 수 있습니다. CMake를 사용할 때 JSON_ImplicitConversions
옵션을 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