8000 GitHub - Mysvac/cpp-jsonlib: A lightweight and efficient C++20 JSON library. · GitHub
[go: up one dir, main page]

Skip to content

Mysvac/cpp-jsonlib

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

94 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

English    中文    lib_compare


Introduction

mysvac-jsonlib is a C++20 JSON library that provides concise and efficient JSON parsing, manipulation, and serialization.

You can find detailed documentation on this repository’s GitHub Pages page.

Importing the Library

Install by vcpkg: ( Update vcpkg port first )

vcpkg install mysvac-jsonlib

CMake:

find_package(mysvac-jsonlib CONFIG REQUIRED)
...
target_link_mysvac_jsonlib(main PRIVATE)

Use in your project:

import std; // use std headers or module
import mysvac.json; // Import the mysvac-jsonlib library
using namespace mysvac; // Use the namespace to simplify code

Basic Types

JSON has six basic types, and this library uses an enum to represent them:

enum class Type{
    eNul = 0,  ///< null type
    eBol,      ///< boolean type
    eNum,      ///< number type
    eStr,      ///< string type
    eArr,      ///< array type
    eObj       ///< object type
};

This enum is located in the mysvac::json namespace, which also contains an important class template Json — a container that can store any JSON data structure.

template<
    bool UseOrderedMap = true,
    template<typename U> class VecAllocator = std::allocator,
    template<typename U> class MapAllocator = std::allocator,
    template<typename U> class StrAllocator = std::allocator
>
class Json;

Although you cannot specify the exact implementations of objects (maps), arrays, and strings, you can specify their memory allocators.

  1. UseOrderedMap: Whether to use an ordered map (std::map) or an unordered map (std::unordered_map).
  2. VecAllocator: Allocator template used by the array type (std::vector).
  3. MapAllocator: Allocator template used by the object (map) type.
  4. StrAllocator: Allocator template used by the string type (std::basic_string).

For convenience, a default Json alias is provided in the mysvac namespace. The rest of this document uses only this default type:

namespace mysvac {
    using Json = ::mysvac::json::Json<>;
}

Inside the class, six aliases are provided for the JSON subtypes:

Alias Default Type Actual Type
Json::Nul std::nullptr_t std::nullptr_t
Json::Bol bool bool
Json::Num double double
Json::Str std::string std::basic_string<...,StrAllocator<char>>
Json::Arr std::vector<Json> std::vector<Json, VecAllocator<Json>>
Json::Obj std::map<std::string, Json> std::map<..,MapAllocator<..>> or std::unordered_map<..,MapAllocator<..>>

These aliases are dependent on the class template parameters and therefore can only be defined inside the class, not in the namespace.

Basic Operations

1. Initialization

The default constructor of Json creates a Nul value, but you can also directly initialize it with any of the six JSON types:

Json null_val;                  // Default constructor, type is Nul
Json bool_val(3.3);             // Floating-point initialization, type is Num
Json obj_val = Json::Obj{};     // Initialize directly as an Obj

In addition to the six JSON types, implicit construction is supported from basic arithmetic types, enum types, and const char*. Enums are treated as integers — do not try to initialize using the json::Type enum, as this will just produce a Num type value:

Json enum_val{ json::Type::eObj }; // Dangerous!
// This will create a Num type, with the numeric value of the enum.

Although Json does not support initializer lists directly, implicit construction allows quick creation of objects using Arr and Obj:

Json smp_val = Json::Obj{
    { "key1", 42 },
    { "key2", "value2" },
    { "key3", true },
    { "arr", Json::Arr{ { 2, 3.14, nullptr } } },
    { "obj", Json::Obj{ { "nested_k", "nested_v" } } }
};

To initialize an empty array, use Arr{}. For non-empty initialization, double curly braces (Arr{{ ... }}) must be used; otherwise, in some cases, it may be interpreted as a copy or expansion constructor rather than an initializer list.

Avoid ( {} ); parentheses can still cause incorrect interpretation.

You can check the type with type() or is_xxx() functions, or get the type name string with type_name():

smp_val.type();                     // Returns json::Type::eObj
json::type_name(smp_val.type());    // Returns "Obj"
smp_val.is_arr();                   // Returns false

There are six is functions: is_arr, is_obj, is_str, is_num, is_bol, and is_nul, corresponding to the six JSON types.

You can reset the value by assignment or with the reset() member function. reset is a template function that defaults to resetting to Nul, but you can explicitly specify a type, e.g., reset<Json::Obj>() resets to an empty object.

The template parameter for reset must be one of the six JSON types; otherwise, compilation will fail.

2. Access and Modification

This library provides the xxx() member function to obtain a reference to the internal data, where xxx is the same as above in is_xxx.

// Note: Obj's mapped type is still Value
Json& vi_42 = smp_val.obj()["key1"];

// Although it returns a reference, it can also be used for assignment
double i_42 = vi_42.num();

// vi_42.str(); // Type mismatch — throws std::bad_variant_access

Json also provides the [] and at operators. The difference is that at prevents out-of-range access (throws an exception), while [] does not check bounds (so Obj can use [] to create a new key-value pair, but Arr out-of-range access is undefined behavior and may cause an assertion failure).

The const version of [] is special — it is equivalent to the const version of at, throwing an exception on out-of-range access instead of creating a new key-value pair or crashing.

smp_val["arr"][1].num(); // returns 3.14
smp_val.at("obj").at("nested_k") = nullptr; // modify object, becomes Nul
smp_val["obj"].at("nested_k").is_nul(); // [] and at can be mixed

Since arrays and objects may require frequent operations, a series of helper functions is provided. These functions return a boolean indicating success and will not throw exceptions.

Function Return Type Description
size() size_t Returns number of elements if array or object, otherwise 0
empty() bool Returns whether array or object is empty; other types return true
contains(key) bool Checks if an object contains a key; non-object returns false
erase(key) bool Deletes key-value pair; returns false if not an object or key not found
erase(index) bool Deletes array element; returns false if not an array or index out-of-range
insert(key, val) bool Inserts key-value pair; returns false if not an object
insert(index,val) bool Inserts array element; returns false if not an array or out-of-range
push_back(val) bool Appends to array; returns false if not an array
pop_back() bool Removes last array element; returns false if not an array or empty

Functions like obj()/arr() can only obtain references of the six JSON types. Therefore, the library also provides to and move templates to obtain the internal value and forcibly convert its type. The former always copies, while the latter performs move or copy (for simple types, or copy when move is unnecessary).

After calling move, the original object’s contents become uncertain — it’s best to reset it manually.

auto str_view = smp_val["key2"].to<std::string_view>(); // returns a view of the internal string
auto str = smp_val["key2"].move<std::string>(); // moves the internal string out; now the internal string is empty, and the previous view is invalid
int int_42 = smp_val["key1"].to<int>(); // returns a copy of the internal integer

Special note: Num data is stored as double. When converting to an integer (including enum types or types meeting integer template requirements), the value is rounded to avoid precision issues.

Both to and move support many types. If conversion fails, a std::runtime_error is thrown. For this reason, the library also provides xx_if and xx_or versions — the former returns optional<T>, and the latter returns a specified default value upon failure.

auto opt_str = smp_val["key1"].to_if<std::string>(); // opt_str is std::optional<std::string>; empty on failure without throwing
if (opt_str) std::cout << *opt_str << std::endl; // print string if conversion succeeded
std::string or_str = smp_val["key1"].to_or<std::string>("default"); // returns "default" if conversion fails

Conversion follows precise rules and a fixed testing order. For details, refer to the GitHub Pages documentation or source code comments.

The library defines three concepts to determine whether a type can be converted (types that meet none of them cannot use to/move and will fail to compile):

  1. json::convertible<J, T> — types T that can be directly converted (includes those satisfying json::json_type<J, T>).
  2. json::convertible_map<J, T, D> — mappable types T that can be indirectly converted, where the key must be a string and the mapped type D satisfies concept 1.
  3. json::convertible_array<J, T, D> — array types T that can be indirectly converted, where the element type D satisfies concept 1.

Here, J refers to a specific instantiation of the mysvac::json::Json class template, e.g., mysvac::Json.

As long as a type meets one of these three concepts, it can be converted using the to and move series functions. We will describe this in detail in the “Custom Type Serialization†section.

Arrays or objects may satisfy multiple concepts at once — this does not affect behavior.

3. Serialization and Deserialization

Serialization and deserialization in this library are highly efficient and easy to use.

For deserialization (parsing), use the static member function Json::parse() to convert a string to a Json object. Note that parse is a class static function (not a namespace function), since different types may require different parsing implementations.

std::string json_str1 = R"( [ 1, false, null, { "Hello": "World" } ] )";
std::string json_str2 = R"( false )"; // top-level can be any JSON type
Json val1 = Json::parse(json_str1).value_or(nullptr); // parse JSON string
std::cout << val1[1].to<bool>() << std::endl; // prints 0 (boolalpha not specified)

Three important notes:

  1. JSON parsing failures are common in real-world scenarios and hard to predict, since invalid formats or garbage data are frequent. Therefore, this function returns std::optional<Json> to avoid exceptions and reduce overhead. There is also an error-type enumeration describing the cause.

  2. An optional max_depth parameter (default 256) limits maximum nesting depth. Although the library guarantees overall parsing complexity is O(n) (single-pass), recursion is used for nested structures, so this limit prevents issues such as stack overflow caused by maliciously deep nesting (e.g., [[[[[[[[]]]]]]]]).

  3. Besides string_view, the function also accepts std::istream for stream-based parsing. Stream parsing is nearly as fast as reading the whole file into a string first, but may use less memory.

For serialization, use the dump/write member functions.

std::string str_ser = val1.dump(); // compact serialization without extra whitespace, returns std::string
std::string str_back;
val1.write(str_back); // append serialization result to std::string
val1.write(std::cout); // output directly to ostream

str_ser and str_back will contain the same data, since dump is implemented using write.

Because Json always represents valid JSON, dump always succeeds. However, write to a stream may fail (e.g., if the file is suddenly closed). In that case, the function stops immediately when fail() is detected and does not throw — you must check the stream state manually.

These serialization functions produce compact JSON without extra whitespace. For more readable output, use the dumpf/writef series (f for “formatâ€), available in all three forms:

std::string pretty_str = val1.dumpf();
val1.writef(std::cout); // output to ostream
// writef to append to a string is also available, omitted here

f-series functions take up to three optional parameters: indentation spaces per level (default 2), initial indent level (default 0). They always succeed unless writing to a failed stream.

Deeply nested structures like [[[[]]]] may cause stack overflow when serializing with indentation, and produce extremely large formatted output. The parser already limits nesting depth, so such structures can only be produced programmatically — avoid creating them.

4. Equality Operator

Json provides an == operator for comparing with another Json. It first checks whether the types match, then compares values ( std::map and std::vector compare recursively based on their elements).

Json val_arr_1 = Json::Arr{{ 1, 2, 3 }};
Json val_arr_2 = Json::Arr{{ 1, 2, 3 }};
Json val_arr_3 = Json::Arr{{ 1, true, 3 }};
val_arr_1 == val_arr_2; // true
val_arr_1 == val_arr_3; // false

More uniquely, Json also supports == comparisons with any other type via templates.

If the type is incompatible, it returns false immediately. If the target type is one of the six JSON types, it first checks for type match, then compares the actual value. Otherwise, it attempts to convert the other type to Json or convert the Json to the target type before comparing. If neither works, it returns false.

== comparisons are guaranteed not to throw exceptions.

Custom Type Serialization

Any custom type that provides a constructor from Json and a type conversion function can interact with JSON data through to/move functions or type conversions, enabling fast serialization and deserialization.

Providing a constructor and a conversion function for Json satisfies the json::convertible concept.

These functions have some subtle requirements and are not trivial to implement manually. Therefore, this library provides a header file containing only macro definitions, which allows you to easily implement JSON interaction for custom types — including support for move semantics.

You may browse this header file yourself; it is minimal but helps you understand what kinds of types satisfy the conversion criteria.

#define M_MYSVAC_JSON_SIMPLIFY_MACROS  // Enable simplified macro function names
#include <mysvac/json_macros.hpp>
// It’s recommended to include this header before other imports, although it only contains macros
import std;
import mysvac.json;
using namespace mysvac;

Suppose you have a type defined as:

struct MyData {
    int id{};
    std::string m_name{};
    bool active{};
    double m_value{};
};

You can add constructors and conversion functions for it via macros, but you must explicitly enable the default constructor:

struct MyData {
    int id{};
    std::string m_name{};
    bool active{false};
    double m_value{};

    MyData() = default; // Default constructor must exist, implementation can be customized

    M_JSON_CV_FUN(MyData,  // Conversion function, must be in public scope
        M_JSON_CV_MEM(id);     // Note the comma after MyData
        M_JSON_CV_MAP(name, m_name)   // Mapping member `m_name` to JSON key `name`
        M_JSON_CV_MEM(active);
        M_JSON_CV_MAP(value, m_value)
    )
    M_JSON_CS_FUN(MyData,  // Constructor function, must be in public scope
        M_JSON_CS_MEM(id);
        M_JSON_CS_MAP(name, m_name);
        M_JSON_CS_MEM_OR(active, true, nullptr); // Default value `true`
        M_JSON_CS_MAP_OR(value, m_value, 64.0, nullptr); // nullptr means no default for sub-elements
    )
};

CV stands for Conversion function, and CS stands for Constructor function. Both macros take the type name as the first argument, followed by a comma. The macro parameters specify the JSON fields involved:

  • MEM means member variable name matches JSON key.
  • MAP means member variable name differs from JSON key.

You may define your own simpler macros, such as JCSM, JCSP, etc., to further reduce boilerplate.

Conversion functions are guaranteed to succeed because they rely on existing member variables. However, the constructor might fail because the corresponding JSON keys might be missing (or the root JSON is not an object), so you need to provide default member values.

You’ll notice that constructor macros with OR suffix take two additional parameters for default values. Macros without OR use the member type’s default constructor as the default value, e.g., decltype(name){}.

It is recommended that default values in the constructor match the member variables’ default values, so that a failed JSON conversion results in the same state as default construction. (Note: The above example sets active's default differently in the constructor, which is discouraged.)

Then you can convert between Json and MyData like this:

Json v_null;
MyData d_null{v_null};  // No data, so all fields use constructor defaults
d_null.active; // true, as specified in constructor

Json v_object{Json::Obj{}};
v_object["id"] = 42;
v_object["name"] = "Test User";
v_object["active"] = false;
v_object["value"] = 128.0;
MyData d_object{v_object};  // Explicit conversion required, no assignment allowed
d_object.m_name == "Test User"; // true

Json v_data{d_object};  // Convert MyData back to JSON object
v_data["id"] == 42; // true

A critical requirement for these macros is that the member variables must support conversion to/from Json.

  1. Fundamental arithmetic types, enums, the six JSON types, and Json itself inherently satisfy this.
  2. Other custom types must provide conversion and constructor functions like above.
  3. Containers (e.g., std::vector, std::list) composed of types meeting conditions 1 or 2 can be used directly.
  4. Maps (e.g., std::map, unordered_map) with keys as std::string and values satisfying 1 or 2 can be used directly.

Conditions 1 and 2 correspond to the concept json::convertible. Conditions 3 and 4 correspond to the concepts json::convertible_array and json::convertible_map, respectively.

For example, since MyData now supports conversion, you can nest it within other types for hierarchical JSON objects:

struct MyData2 {
    std::string name;             // std::string equals json::Str, usable directly
    MyData my_data;               // MyData has conversion functions, usable directly
    std::vector<MyData> data_list; // Lists of convertible types also work (but nested lists don’t)
    MyData2() = default;

    M_JSON_CV_FUN(MyData2,
        M_JSON_CV_MEM(name);
        M_JSON_CV_MAP(data, my_data);
        M_JSON_CV_MEM(data_list);
    )
    M_JSON_CS_FUN(MyData2,
        M_JSON_CS_MEM(name);
        M_JSON_CS_MAP(data, my_data);
        M_JSON_CS_MEM_OR(data_list, std::vector<MyData>{}, MyData{}); // member default, sub-element default
    )
};

Here, you’ll see the fourth parameter in the OR macro: the first is the member default, the second is the default for child elements (used only when the target is an array or map, but not a Json::Arr or Json::Obj). For other types, you can just use nullptr.

For example, if an array is expected but the JSON is not an array, the member default is returned. If JSON is an array but some elements cannot be converted, those elements are replaced by the sub-element default to maintain array length.

You can convert back and forth between the types as follows:

Json v_data2{MyData2{}};
std::println("");
v_data2.writef(std::cout);
std::println("");
v_data2["data"]["id"] = 8848;
v_data2["data"]["name"] = "Mount Everest";
v_data2["data"]["active"] = true;
v_data2["data_list"].push_back(v_data2["data"]);
v_data2["name"] = "name_name";
MyData2 d_data2{v_data2};

M_EXPECT_TRUE(d_data2.my_data.id == 8848);             // true
M_EXPECT_TRUE(d_data2.my_data.m_name == "Mount Everest"); // true
M_EXPECT_TRUE(d_data2.data_list.size() == 1);          // true
M_EXPECT_TRUE(d_data2.data_list[0].id == 8848);         // true
M_EXPECT_TRUE(d_data2.data_list[0].m_name == "Mount Everest"); // true
M_EXPECT_TRUE(d_data2.name == "name_name");             // true

The M_EXPECT_TRUE macro is from the vct-test-unit library and can be ignored if unfamiliar.

List and Map Extensions

As the complexity grows, this final section explains details about lists and maps.

Previously, only six JSON types and basic arithmetic types could directly convert to/from Json. Custom types needed conversion functions and constructors—i.e., only types satisfying json::convertible could convert.

But what about standard types like std::array<int>? The internal int satisfies the condition, but the container itself does not and cannot provide conversion functions.

Hence, this library provides four template functions for conversions between:

  • Array types and Json
  • Map types and Json

Which types can use these templates?

  • For maps, the key must be std::string or convertible to it.
  • The value type must satisfy json::convertible (i.e., able to convert directly).

This distinction leads to separate concepts: json::convertible_map and json::convertible_array.

Converting array/map → Json will not lose any elements because all are convertible to JSON. But Json → array/map may lose elements due to invalid data or formats in the JSON.

Thus, if your array or map is not a Json::Arr or Json::Obj, you must provide two default values during conversion:

  1. The default return value if the JSON structure doesn’t match (e.g., expected array but JSON isn’t an array).
  2. The default for child elements when partial matches fail (e.g., for vector<int> if JSON is [1, 2, [], 3], specify the default int to fill for []).

This explains why macros like M_JSON_CS_MEM_OR and M_JSON_CS_MAP_OR need two default values. If the target type is not an array or map, the child-element default can be anything (commonly nullptr).

This mechanism corresponds to to/move functions internally.

For basic or custom types, conversion is straightforward:

xxx = val.to<MyData>();  // or move<MyData>()

For arrays, you must specify the child-element default explicitly:

// Two template parameters: target type, child element type
xxx = val.to<std::vector<MyData>, MyData>(MyData{});
// Template argument deduction also works
xxx = val.to<std::vector<MyData>>(MyData{});

The child-element default defaults to Json::Nul (i.e., std::nullptr_t). If your target isn’t an array or object, you don’t need to specify it.

Note that to/move throws exceptions on complete mismatches, so only child-element defaults are specified. Macros use to_or and move_or which require two default values:

// Second template parameter deduced automatically
xxx = val.to_or<std::vector<MyData>>(std::vector<MyData>{}, MyData{});

Conclusion

This covers the basic usage of the library. Although custom type serialization is somewhat complex, you can study the documentation and source code — the entire source is under 2000 lines.

Focus on the implementation of to and move functions and the macro definitions in the headers to get started quickly.

If you find any issues or improvements, please consider submitting an issue or pull request.


介ç»

mysvac-jsonlib 是一个 C++20 çš„ JSON 库,它æä¾›ç®€æ´ã€é«˜æ•ˆçš„ JSON è§£æžã€æ“作和åºåˆ—化功能。

ä½ å¯ä»¥åœ¨æœ¬ä»“库的 github-pages 页颿‰¾åˆ°è¯¦ç»†çš„æ–‡æ¡£ã€‚

导入库

使用 vcpkg å®‰è£…åº“ï¼ˆéœ€è¦æœ€æ–°ç‰ˆæœ¬ï¼‰ï¼š

vcpkg install mysvac-jsonlib

CMakeé…ç½®:

find_package(mysvac-jsonlib CONFIG REQUIRED)
...
target_link_mysvac_jsonlib(main PRIVATE)

在项目中使用:

import std; // 使用标准库头文件或标准库模å—
import mysvac.json; // 导入 mysvac-jsonlib 库
using namespace mysvac; // 使用命å空间,简化代ç ä¹¦å†™

基础类型

JSON 存在六ç§åŸºæœ¬ç±»åž‹ï¼Œæœ¬åº“使用了一个枚举æ¥è¡¨ç¤ºå®ƒä»¬ï¼š

enum class Type{
    eNul = 0,  ///< null type
    eBol,      ///< boolean type
    eNum,    ///< number type
    eStr,    ///< string type
    eArr,     ///< array type
    eObj     ///< object type
};

此枚举ä½äºŽ mysvac::json 命å空间中,这里é¢è¿˜æœ‰ä¸€ä¸ªé‡è¦çš„ç±»æ¨¡æ¿ Json ,它是一个容器,å¯ä»¥å­˜å‚¨ä»»æ„ä¸€ç§ JSON æ•°æ®ç»“构。

template<
    bool UseOrderedMap = true,
    template<typename U> class VecAllocator = std::allocator,
    template<typename U> class MapAllocator = std::allocator,
    template<typename U> class StrAllocator = std::allocator
>
class Json;

è™½ç„¶ä½ æ— æ³•æŒ‡å®šå¯¹è±¡ï¼ˆæ˜ å°„ï¼‰ã€æ•°ç»„和字符串的具体实现,但å¯ä»¥ä¸ºå®ƒä»¬æŒ‡å®šå†…存分é…器。

  1. UseOrderedMap : ä½¿ç”¨æœ‰åºæ˜ å°„std::map,还是无åºçš„std::unordered_map。
  2. VecAllocator : 数组类型std::vector使用的分é…器模æ¿ã€‚
  3. MapAllocator : 对象类型(映射)使用的分é…器模æ¿ã€‚
  4. StrAllocator : 字符串类型std::basic_string使用的分é…器模æ¿ã€‚

为了方便æ“作,我们在 mysvac 命å空间中æä¾›äº†ä¸€ä¸ªé»˜è®¤çš„ Json 别å,本文档åŽç»­å†…容仅使用这个默认类型:

namespace mysvac {
    using Json = ::mysvac::json::Json<>;
}

类内部æä¾›äº†å…­ç§ JSON å­ç±»åž‹çš„别å:

别å 默认类型 实际类型
Json::Nul std::nullptr_t std::nullptr_t
Json::Bol bool bool
Json::Num double double
Json::Str std::string std::basic_string<...,StrAllocator<char>>
Json::Arr std::vector<Json> std::vector<Json, VecAllocator<Json>>
Json::Obj std::map<std::string, Json> std::map<..,MapAllocator<..>> 或 std::unordered_map<..,MapAllocator<..>>

这些类型的具体定义与类模æ¿å½¢å‚有关,因此别ååªèƒ½æ”¾åœ¨ç±»å†…,而éžå‘½å空间中。

基础æ“作

1. åˆå§‹åŒ–

Value 的默认构造函数会创建 Nul 类型的值,但你å¯ä»¥ç›´æŽ¥é€šè¿‡ä¸Šè¿°å…­ç§ç±»åž‹è¿›è¡Œåˆå§‹åŒ–:

Json null_val;                  // 默认构造,类型为 Nul
Json bool_val(3.3);             // 浮点åˆå§‹åŒ–,类型为 Num
Json obj_val = Json::Obj{};  // 直接使用 Obj åˆå§‹åŒ–

é™¤äº†ä¸Šè¿°å…­ç§ JSON 类型,我们还支æŒä½¿ç”¨åŸºæœ¬ç®—æœ¯ç±»åž‹ã€æžšä¸¾ç±»åž‹å’Œ const char* 进行éšå¼æž„造。 枚举会被视为整数,ä¸è¦è¯•图使用 json::Type 枚举值进行指定åˆå§‹åŒ–,这åªä¼šç”Ÿæˆ Num 类型的值:

Json enum_val{ json::Type::eObj }; // å±é™©
// 这会生æˆä¸€ä¸ª Num 类型的值,具体值å–决于枚举值的整数表示。

虽然 Json 䏿”¯æŒåˆå§‹åŒ–列表,但由于éšå¼æž„造的存在,å¯ä»¥é€šè¿‡ Arr å’Œ Obj çš„åˆå§‹åŒ–列表快速创建对象:

Json smp_val = Json::Obj{
    { "key1", 42 },
    {"key2", "value2"},
    {"key3", true },
    {"arr", Json::Arr{ { 2, 3.14, nullptr } } },
    {"obj", Json::Obj{ {"nested_k", "nested_v"} } }
};

空数组åˆå§‹åŒ–请使用 Arr{} ,éžç©ºåˆå§‹åŒ–必须使用 Arr{ { ... } } åŒé‡å¤§æ‹¬å·ï¼Œå¦åˆ™åœ¨ç‰¹å®šæƒ…å†µä¸‹ä¼šè¢«è®¤ä¸ºæ˜¯æ‹·è´æž„造或扩容构造而éžåˆå§‹åŒ–列表。

请ä¸è¦ä½¿ç”¨ ( {} ) ï¼Œå°æ‹¬å·ä¾ç„¶å¯èƒ½è¯†åˆ«é”™è¯¯ã€‚

ä½ å¯ä»¥ç”¨ type() 或 is_xxx() 函数检查类型,或者使用 type_name() 获å–字符串形å¼çš„类型å:

smp_val.type();        // 返回 json::Type::eObj
json::type_name(smp_val.type());  // 返回 "Obj"
smp_val.is_arr();     // 返回 false

is 共有六个,分别是 arrã€objã€strã€numã€bol å’Œ nul ï¼Œå¯¹åº”å…­ç§ JSON 类型。

ä½ å¯ä»¥é€šè¿‡èµ‹å€¼è¯­å¥é‡ç½®å†…容,也å¯ä»¥ä½¿ç”¨ reset() æˆå‘˜å‡½æ•°ã€‚ 这是个模æ¿å‡½æ•°ï¼Œé»˜è®¤é‡ç½®å›ž Nul 类型,但你å¯ä»¥æ˜¾ç¤ºæŒ‡å®šé‡ç½®ç±»åž‹ï¼Œæ¯”如使用 reset<Json::Obj>() 将内容é‡ç½®ä¸ºä¸€ä¸ªç©ºçš„ Obj 。

reset 的模æ¿å½¢å‚åªèƒ½æ˜¯å…­ç§ JSON 类型之一,å¦åˆ™æ— æ³•通过编译。

2. 访问和修改

本库æä¾›äº† xxx() æˆå‘˜å‡½æ•°ä»¥èŽ·å–内部数æ®çš„引用,xxx 和上é¢çš„ is_xxx 相åŒã€‚

 // æ³¨æ„ Obj çš„ mapped ä¾ç„¶æ˜¯ Value 类型
Json& vi_42 = smp_val.obj()["key1"];

// 虽然返回引用,但也å¯ä»¥ç”¨äºŽèµ‹å€¼
double i_42 = vi_42.num(); 

 // vi_42.str(); // 类型ä¸åŒ¹é…,抛出 std::bad_varient_access 异常

Json 还æä¾›äº† [] å’Œ at è¿ç®—符,区别在于 at ç¦æ­¢ç´¢å¼•越界(抛出异常),而 [] 䏿£€æŸ¥è¶Šç•Œï¼ˆæ‰€ä»¥Objå¯ä»¥ç”¨[]创建新键值对,但Arr越界是未定义行为,å¯èƒ½ç›´æŽ¥æ–­è¨€å´©æºƒï¼‰ã€‚

const çš„ [] 较为特殊,等价于 const çš„ at ,越界抛出异常而ä¸ä¼šåˆ›å»ºæ–°é”®å€¼å¯¹æˆ–崩溃。

smp_val["arr"][1].num(); // 返回 3.14
smp_val.at("obj").at("nested_k") = nullptr; // 修改对象,å˜ä¸º Nul
smp_val["obj"].at("nested_k").is_nul(); // [] å’Œ at å¯ä»¥æ··åˆä½¿ç”¨

数组和映射类型å¯èƒ½éœ€è¦é¢‘ç¹æ“作,因此我们æä¾›äº†ä¸€ç³»åˆ—辅助函数。这些函数返回一个布尔值表示æ“ä½œæ˜¯å¦æˆåŠŸï¼Œä¸ä¼šæŠ›å‡ºå¼‚常。

函数å 返回值 说明
size() size_t 数组或映射则返回元素数,å¦åˆ™è¿”回 0
empty() bool 返回数组或映射是å¦ä¸ºç©ºï¼Œå…¶ä»–类型返回 true
contains(key) bool 判断是å¦å«æŸä¸ªé”®ï¼Œéžæ˜ å°„返回 false
erase(key) bool åˆ é™¤é”®å€¼å¯¹ï¼Œéžæ˜ å°„或键ä¸å­˜åœ¨è¿”回 false
erase(index) bool åˆ é™¤æ•°ç»„å…ƒç´ ï¼Œéžæ•°ç»„或索引越界返回 false
insert(key, val) bool æ’å…¥é”®å€¼å¯¹ï¼Œéžæ˜ å°„返回 false
insert(index,val) bool æ’å…¥æ•°ç»„å…ƒç´ ï¼Œéžæ•°ç»„或越界返回 false
push_back(val) bool 呿•°ç»„æœ«å°¾æ·»åŠ å…ƒç´ ï¼Œéžæ•°ç»„返回 false
pop_back() bool åˆ é™¤æ•°ç»„æœ«å°¾å…ƒç´ ï¼Œéžæ•°ç»„或空数组返回 false

obj()/arr() 等函数åªèƒ½èŽ·å–å…­ç§å¼•用,所以本库还æä¾›äº† to å’Œ move æ¨¡æ¿æ¥èŽ·å–内部的值并强制转æ¢ç±»åž‹ã€‚ å‰è€…必然是拷è´ï¼ŒåŽè€…是移动或拷è´ï¼ˆç®€å•类型,或者无需移动时进行拷è´ï¼‰ã€‚

调用 move åŽéš¾ä»¥ç¡®å®šåŽŸå¯¹è±¡çš„å†…å®¹ï¼Œæœ€å¥½ä¸»åŠ¨ reset。

auto str_view = smp_val["key2"].to<std::string_view>(); // 返回内部字符串的视图
auto str = smp_val["key2"].move<std::string>(); // 将内部字符串移动了出æ¥ï¼ŒçŽ°åœ¨å†…éƒ¨å˜ä¸ºç©ºå­—符串,之å‰çš„视图ä¸å†å¯ç”¨
int int_42 = smp_val["key1"].to<int>(); // 返回内部整数的拷è´

特别注æ„,Num æ•°æ®ä½¿ç”¨ double å­˜å‚¨ï¼Œå› æ­¤åœ¨è½¬æ¢æˆæ•´æ•°æ—¶ï¼ˆæžšä¸¾ç±»åž‹ï¼Œæˆ–ç¬¦åˆæ•´æ•°æ¨¡æ¿è¦æ±‚的类型),会四èˆäº”入,é¿å…精度问题。

注æ„,to å’Œ move 支æŒå¾ˆå¤šç±»åž‹ï¼Œè½¬æ¢å¤±è´¥ä¼šæŠ›å‡º std::runtime_error 异常。 为此,我们还æä¾›äº† xx_if å’Œ xx_or 版本,å‰è€…返回 optional<T> ,åŽè€…则是在失败时返回指定的默认值。

auto opt_str = smp_val["key1"].to_if<std::string>(); // opt_str 是 std::optional<std::string> ,转æ¢å¤±è´¥ä¸ºç©ºï¼Œä½†ä¸æŠ›å‡ºå¼‚常
if(opt_str) std::cout << *opt_str << std::endl; // å¦‚æžœè½¬æ¢æˆåŠŸï¼Œè¾“å‡ºå­—ç¬¦ä¸²
std::string or_str = smp_val["key1"].to_or<std::string>("default"); // 如果转æ¢å¤±è´¥ï¼Œè¿”回 "default"

转æ¢å…·æœ‰éžå¸¸å‡†ç¡®çš„规则与测试顺åºï¼Œè¯¦ç»†å†…容请å‚考 github-pages 文档,或æºç æ³¨é‡Šã€‚

本库æä¾›äº†ä¸‰ä¸ªæ¦‚念用于查询类型是å¦å¯èƒ½è¢«è½¬æ¢ï¼šï¼ˆéƒ½ä¸æ»¡è¶³çš„æ— æ³•使用to/move模æ¿ï¼Œæ— æ³•通过编译)

  1. json::convertible<J, T> å¯èƒ½ç›´æŽ¥è½¬æ¢æˆåŠŸçš„ç±»åž‹ T,这包å«äº†æ»¡è¶³ json::json_type<J, T> 的类型。
  2. json::convertible_map<J, T,D> å¯èƒ½é—´æŽ¥è½¬æ¢æˆåŠŸçš„æ˜ å°„ç±»åž‹ T,键必须是字符串,值(mapped)是 D 类型且满足æ¡ä»¶ 1 。
  3. json::convertible_array<J, T,D> å¯èƒ½é—´æŽ¥è½¬æ¢æˆåŠŸçš„æ•°ç»„ç±»åž‹ T,内部值是 D 类型且满足æ¡ä»¶ 1 。

这里的 J 是指 mysvac::json::Json 类模æ¿çš„具体实例化类型,比如 mysvac::Json 。

åªè¦ç±»åž‹æ»¡è¶³ä¸‰ç§æ¦‚念之一,就å¯ä»¥ä½¿ç”¨ to å’Œ move 系列函数进行转æ¢ã€‚我们会在åŽç»­çš„“自定义类型åºåˆ—化â€éƒ¨åˆ†è¯¦ç»†ä»‹ç»ã€‚

数组或映射å¯èƒ½åŒæ—¶æ»¡è¶³å¤šç§æ¦‚念,但这ä¸å½±å“效果。

3. åºåˆ—化与ååºåˆ—化

本库的åºåˆ—化和ååºåˆ—化éžå¸¸é«˜æ•ˆä¸”容易使用。

首先是ååºåˆ—化,将字符串转æ¢ä¸º Json 对象,使用 Json::parse() 函数。 æ³¨æ„ parse æ˜¯ç±»çš„é™æ€æˆå‘˜å‡½æ•°ï¼Œè€Œéžå‘½å空间中的函数,因为ä¸åŒç±»åž‹éœ€è¦ä¸åŒçš„è§£æžå‡½æ•°ã€‚

std::string json_str1 = R"( [ 1, false, null, { "Hello": "World" } ] )";
std::string json_str2 = R"( false )"; // å…许顶层类型是任一 JSON 类型
Json val1 = Json::parse(json_str1).value_or( nullptr ); // è§£æž JSON 字符串
std::cout << val1[1].to<bool>() << std::endl; // 输出 0 (没有指定 boolaplha)

这里还需è¦è¯´æ˜Žä¸‰ä»¶äº‹ï¼š

  1. JSON 文件解æžå¤±è´¥åœ¨å®žé™…应用中很常è§ä¸”难以预料,因为很容易有一些格å¼é”™è¯¯æˆ–垃圾数æ®ã€‚ 因此本库的ååºåˆ—化函数返回 std::optional<Json> ,从而é¿å…使用异常机制,å‡å°å¼€é”€ã€‚åŽè€…是一个æè¿°é”™è¯¯ç±»åž‹çš„æžšä¸¾ã€‚

  2. 此函数还具有一个å¯é€‰å‚æ•° max_depth(默认是 256),用于é™åˆ¶è§£æžçš„æœ€å¤§ï¼ˆåµŒå¥—)深度。 本库虽然ä¿è¯æ€»è§£æžå¤æ‚度是 O(n) çš„ï¼ˆä¸¥æ ¼å•æ¬¡é历),但使用了递归æ¥å¤„ç†åµŒå¥—结构,因此需è¦ç”¨å®ƒé¿å…æŸäº›åžƒåœ¾æ•°æ®çš„问题(比如过长的 [[[[[[[[]]]]]]]] 在递归时å¯èƒ½å¯¼è‡´æ ˆæº¢å‡ºï¼‰ã€‚

  3. 此函数除了 string_view 外,还能传入 std::istream 进行æµå¼è§£æžã€‚ æµå¼è§£æžæ–‡ä»¶çš„æ•ˆçŽ‡å‡ ä¹Žç­‰åŒäºŽå…ˆå°†æ–‡ä»¶å…¨éƒ¨è¯»å…¥ string å†ç”¨ string è§£æžï¼Œä½†å†…å­˜å ç”¨å¯èƒ½æ›´å°‘。

ç„¶åŽæ˜¯åºåˆ—化,使用 Json 对象的 dump/write æˆå‘˜å‡½æ•°ã€‚

std::string str_ser = val1.dump(); // ä¸å«æ— æ•ˆç©ºç™½å­—符的高效åºåˆ—化,返回 std::string
std::string str_back;
val1.write( str_back ); // å°†åºåˆ—化结果写入 std::string 的末尾
val1.write( std::cout ); // å°†åºåˆ—化结果直接输出到 `ostream` 中

现在 str_ser 和 str_back 的内容完全一样,因为 dump 就是用 write 实现的。

由于 Json 必然是有效的 JSON æ•°æ®ï¼Œå› æ­¤ dump å¿…ç„¶æˆåŠŸã€‚ ä¸è¿‡ write çš„æµæ“作ä¸ä¸€å®šæˆåŠŸï¼ˆæ¯”å¦‚æ–‡ä»¶çªç„¶å…³é—­ï¼‰ã€å‡½æ•°æ£€æµ‹åˆ°æµçš„状æ€ä¸º fail() åŽä¼šç«‹å³è¿”回,但ä¸ä¼šæŠ›å‡ºå¼‚常,需è¦ä½ è‡ªè¡Œæ£€æŸ¥æµçš„状æ€ã€‚

上é¢çš„三个åºåˆ—化函数都是高效的紧凑åºåˆ—化,ä¸å«æ— æ•ˆç©ºç™½å­—符。 但你å¯ä»¥ä½¿ç”¨ dumpf/writef 系列函数æ¥èŽ·å¾—æ›´æ˜“è¯»çš„æ ¼å¼åŒ–输出,f 指 format ï¼Œå®ƒåŒæ ·åŒ…å«ä¸‰ç§å½¢å¼ï¼š

std::string pretty_str = val1.dumpf();
val1.writef( std::cout ); // 输出到 `ostream`
// 还有 writef 写入字符串末尾,此处çœç•¥

f 系列有三个å¯é€‰å‚æ•°ï¼Œä¾æ¬¡æ˜¯ â€œå•æ¬¡ç¼©è¿›ç©ºæ ¼æ•°ï¼ˆé»˜è®¤ 2)â€ï¼Œâ€œåˆå§‹ç¼©è¿›æ¬¡æ•°ï¼ˆé»˜è®¤ 0)â€ã€‚ è¿™äº›å‡½æ•°åŒæ ·å¿…ç„¶æˆåŠŸï¼ˆé™¤éžå†™å…¥æµä½†æµçжæ€å¼‚常)。

一些特殊数æ®å¦‚ [[[[]]]] 这样的深度嵌套结构,åºåˆ—化时å¯èƒ½å¯¼è‡´é€’归过深栈溢出,且带缩进的格å¼åŒ–文本会éžå¸¸å¤§ã€‚ 我们在ååºåˆ—化函数中é™åˆ¶äº†åµŒå¥—深度,因此你åªå¯èƒ½åœ¨ç¨‹åºä¸­ä¸»åŠ¨æž„é€ è¿™æ ·çš„ç‰¹æ®Šæ•°æ®è€Œä¸å¯èƒ½ä»Žå¤–部输入,å¯ä»¥ä¸»åЍé¿å…构造此类数æ®ã€‚

4. 等于è¿ç®—符

Json 类型æä¾›äº†å’Œ Json 进行比较的 == è¿ç®—符,它首先判断类型是å¦ç›¸åŒï¼Œç„¶åŽè°ƒç”¨å†…部的 == 进行比较( std::map å’Œ std::vector 的比较基于å­å…ƒç´ å†…容,从而实现递归比较)。

Json val_arr_1 = Json::Arr{{ 1, 2, 3 }};
Json val_arr_2 = Json::Arr{{ 1, 2, 3 }};
Json val_arr_3 = Json::Arr{{ 1, true, 3 }};
val_arr_1 == val_arr_2; // true
val_arr_1 == val_arr_3; // false

更加特殊的是, Json 还通过模æ¿å‡½æ•°å®žçŽ°äº†å’Œå…¶ä»–ä»»æ„类型的 == 比较。

ä¸å…¼å®¹çš„类型直接返回 false ï¼Œå¦‚æžœç›®æ ‡æ˜¯å…­ç§ JSON 类型之一,则先测试类型是å¦åŒ¹é…ï¼Œç„¶åŽæ¯”较具体值。 å¦åˆ™ï¼Œå°è¯•将对象转æ¢ä¸º Json ,或者将 Json 转æ¢ä¸ºç›®æ ‡ç±»åž‹ç„¶åŽæ¯”较。都ä¸åŒ¹é…则返回 false 。

== æ“作必然æˆåŠŸï¼Œä¸ä¼šæŠ›å‡ºå¼‚常。

自定义类型åºåˆ—化

任何自定义类型,åªè¦æä¾›é’ˆå¯¹ Json 的构造函数和类型转æ¢å‡½æ•°ï¼Œå°±èƒ½é€šè¿‡ to/move 或者类型转æ¢ç­‰æ–¹å¼ä¸Ž JSON æ•°æ®äº¤äº’,从而实现快速的åºåˆ—化和ååºåˆ—化。

æä¾›äº†é’ˆå¯¹ Json 构造函数和类型转æ¢å‡½æ•°ï¼Œå°±æ»¡è¶³äº† json::convertible 概念。

è¿™äº›å‡½æ•°è¿˜æœ‰ä¸€äº›ç»†èŠ‚è¦æ±‚,它们的实现并ä¸è½»æ¾ï¼Œå› æ­¤æœ¬åº“æä¾›äº†ä¸€ä¸ªä»…包å«å®å®šä¹‰çš„头文件,让你å¯ä»¥è½»æ¾å®žçŽ°è‡ªå®šä¹‰ç±»åž‹ä¸Ž JSON 的交互,它甚至支æŒç§»åŠ¨è¯­ä¹‰ã€‚

ä½ å¯ä»¥è‡ªè¡Œæµè§ˆæ­¤å¤´æ–‡ä»¶ï¼Œå®ƒçš„内容很少,但å¯ä»¥è®©ä½ äº†è§£ä»€ä¹ˆç±»åž‹èƒ½å¤Ÿæ»¡è¶³è½¬æ¢æ¡ä»¶ã€‚

#define M_MYSVAC_JSON_SIMPLIFY_MACROS // 定义å®ï¼Œä»¥å¯ç”¨ç®€åŒ–çš„å®å‡½æ•°å
#include <mysvac/json_macros.hpp>
// 建议将所有头文件放在所有 import 之å‰ï¼Œè™½ç„¶æ­¤æ–‡ä»¶ä»…å«å®å®šä¹‰
import std;
import mysvac.json;
using namespace mysvac;

å‡è®¾ä½ çŽ°åœ¨æœ‰è¿™æ ·ä¸€ä¸ªç±»åž‹ï¼š

struct MyData{
    int id{};
    std::string m_name{};
    bool active{};
    double m_value{};
};

ç„¶åŽä½ å¯ä»¥åƒä¸‹é¢è¿™æ ·ï¼Œé€šè¿‡å®å®šä¹‰ä¸ºå…¶æ·»åŠ æž„é€ å‡½æ•°å’Œè½¬æ¢å‡½æ•°ï¼Œä½†éœ€è¦æ˜¾å¼å¯ç”¨é»˜è®¤æž„造:

struct MyData{
    int id{};
    std::string m_name{};
    bool active{false};
    double m_value{};

    MyData() = default; // 必须存在默认构造,内容å¯ä»¥è‡ªå®šä¹‰

    M_JSON_CV_FUN( MyData,  // 转æ¢å‡½æ•°ï¼Œå¿…须在 public 作用域
        M_JSON_CV_MEM( id );    // 注æ„,MyData åŽé¢å¿…须有 `,` 
        M_JSON_CV_MAP( name, m_name )   // 但是剩余的字段åŽé¢ä¸èƒ½æœ‰é€—å· `,` ï¼Œåˆ†å· `;` 则是å¯é€‰çš„ 
        M_JSON_CV_MEM( active )
        M_JSON_CV_MAP( value, m_value )
    )
    M_JSON_CS_FUN( MyData,  // 构造函数,必须在 public 作用域
        M_JSON_CS_MEM( id )
        M_JSON_CS_MAP( name, m_name )
        M_JSON_CS_MEM_OR( active, true, nullptr ) // 默认值是 `true`
        M_JSON_CS_MAP_OR( value, m_value, 64.0, nullptr ) // nullptr 表示此类型ä¸éœ€è¦å­å…ƒç´ é»˜è®¤å€¼
    )
};

CV 的是指 Conversion 转æ¢å‡½æ•°ï¼Œè€Œ CS 是指 Constructor æž„é€ å‡½æ•°ã€‚å®ƒä»¬çš„ç¬¬ä¸€ä¸ªå‚æ•°éƒ½æ˜¯ç±»åž‹å,åŽé¢éœ€è¦ä¸€ä¸ª , 分隔符。 ç„¶åŽé€šè¿‡å¯¹åº”çš„å®å®šä¹‰æŒ‡å®š JSON 转æ¢ä¸­éœ€è¦çš„字段,MEM 是指æˆå‘˜å˜é‡å与 JSON é”®å相åŒï¼ŒMAP 是指æˆå‘˜å˜é‡å与 JSON é”®åä¸åŒï¼ˆæ¯”如键是 name ,而æˆå‘˜å˜é‡å是 m_name)。

ä½ å¯ä»¥é€‰æ‹©è‡ªè¡Œå®šä¹‰ä¸€äº›ç®€åŒ–å®ï¼Œæ¯”如 JCSM JCSP 等等,高度简化书写。

转æ¢å‡½æ•°æ˜¯å¿…ç„¶æˆåŠŸçš„ï¼Œå› ä¸ºéœ€è¦çš„æ•°æ®éƒ½æ˜¯æˆå‘˜å˜é‡ã€‚ 但是构造函数中的æˆå‘˜èµ‹å€¼å¯èƒ½ä¼šå¤±è´¥ï¼Œå› ä¸º Json 中å¯èƒ½ä¸å­˜åœ¨å¯¹åº”的键(甚至 Json æ ¹æœ¬ä¸æ˜¯ Obj ç±»åž‹ï¼‰ï¼Œå› æ­¤éœ€è¦æŒ‡å®šæˆå‘˜é»˜è®¤å€¼ã€‚

你会看到构造函数(CS)的å®ï¼Œéƒ¨åˆ†å¸¦æœ‰ OR åŽè€…ï¼Œå®ƒä»¬å¤šäº†ä¸¤ä¸ªå‚æ•°ï¼Œç¬¬ä¸€ä¸ªå‚数就是默认值。 而没有 OR çš„å®å¹¶éžæ²¡æœ‰é»˜è®¤å€¼ï¼Œè€Œæ˜¯å°†å¯¹åº”ç±»åž‹çš„é»˜è®¤æž„é€ ä½œä¸ºé»˜è®¤å€¼ï¼Œå³ decltype(name){} 。

作者建议是,字段的默认值请和æˆå‘˜å˜é‡çš„é»˜è®¤å€¼ä¿æŒä¸€è‡´ï¼Œå› ä¸ºæˆ‘们希望从 Json 转æ¢å¤±è´¥çš„结果等于默认构造函数的效果。 ï¼ˆä¸Šé¢ active 的默认值就和 CS 中指定的ä¸ä¸€æ ·ï¼Œä¸æŽ¨èè¿™ç§å†™æ³•)

ç„¶åŽä½ å°±å¯ä»¥åƒä¸‹é¢è¿™æ ·ï¼Œè®© Json å’Œ MyData 互相转æ¢äº†ï¼š

Json v_null; 
MyData d_null{ v_null }; // 什么都没有,因此全部字段都是 CS 中的默认值
d_null.active; // true,因为 CS 函数指定了默认值为 true

Json v_object{ Json::Obj{} };
v_object["id"] = 42;
v_object["name"] = "Test User";
v_object["active"] = false;
v_object["value"] = 128.0;
MyData d_object{ v_object };    // 必须显å¼è½¬æ¢ï¼Œä¸èƒ½ç”¨ `=` 构造
d_object.m_name == "Test User"; // true

Json v_data{ d_object }; // å°† MyData 转æ¢ä¸º JSON 对象
v_data["id"] == 42; // true

ä½¿ç”¨è¿™ä¸¤ä¸ªå®æœ‰ä¸€ä¸ªéžå¸¸é‡è¦çš„è¦æ±‚,å³éœ€è¦è½¬æ¢çš„æˆå‘˜å˜é‡å¿…须支æŒä¸Ž Json 类型的转æ¢ã€‚

  1. å¯¹äºŽåŸºæœ¬ç®—æœ¯ç±»åž‹ã€æžšä¸¾ç±»åž‹ã€å…­ç§ JSON 类型和 Json è‡ªèº«ï¼Œå¿…ç„¶æ»¡è¶³è¦æ±‚。
  2. 对于其他自定义类类型,需è¦åƒä¸Šé¢ä¸€æ ·æä¾›è½¬æ¢å‡½æ•°å’Œæž„造函数。
  3. 对于满足æ¡ä»¶ 1 或 2 的类型构æˆçš„列表(如 std::vector,std::list 等),å¯ä»¥ç›´æŽ¥ä½¿ç”¨ã€‚
  4. 对于满足æ¡ä»¶ 1 或 2 的类型构æˆçš„æ˜ å°„(如 std::map,unordered_map 等),在键为 std::string 时也å¯ä»¥ç›´æŽ¥ä½¿ç”¨ã€‚

æ¡ä»¶ 1 å’Œ 2 指的是概念 json::convertible ,而æ¡ä»¶ 3 å’Œ 4 指的是概念 json::convertible_array å’Œ json::convertible_map 。

比如,现在的 MyData 类型已ç»é€šè¿‡å®å®šä¹‰æä¾›äº†è½¬æ¢å‡½æ•°å’Œæž„造函数,满足æ¡ä»¶ 2 。 因此你å¯ä»¥åœ¨å…¶ä»–类型中直接使用它,然åŽå®žçŽ°åµŒå¥—çš„ JSON 对象:

struct MyData2 {
    std::string name;   // std::string 等于 json::Str,因此å¯ä»¥ç›´æŽ¥ä½¿ç”¨
    MyData my_data;     // MyData å·²ç»æœ‰è½¬æ¢å‡½æ•°å’Œæž„造函数,因此å¯ä»¥ç›´æŽ¥ä½¿ç”¨
    std::vector<MyData> data_list;  // 能够直接使用的类型构æˆçš„列表也能直接使用(但å†å¥—一层列表就ä¸è¡Œäº†ï¼‰
    MyData2() = default;
    M_JSON_CV_FUN( MyData2,
        M_JSON_CV_MEM( name )
        M_JSON_CV_MAP( data, my_data )
        M_JSON_CV_MEM( data_list )
    )
    M_JSON_CS_FUN( MyData2,
        M_JSON_CS_MEM( name )
        M_JSON_CS_MAP( data, my_data )
        M_JSON_CS_MEM_OR( data_list, std::vector<MyData>{}, MyData{} ) // å˜é‡å,默认值,内部å­å…ƒç´ çš„默认值
    )
};

å¯ä»¥çœ‹åˆ°æˆ‘们用到了 OR å®çš„ç¬¬å››ä¸ªå‚æ•°ã€‚ç¬¬ä¸‰ä¸ªå‚æ•°æ˜¯å­—æ®µæœ¬èº«çš„é»˜è®¤å€¼ï¼Œç¬¬å››ä¸ªå‚æ•°æ˜¯å­å…ƒç´ çš„默认值。 ç¬¬å››ä¸ªå‚æ•°ä»…åœ¨ç›®æ ‡æ˜¯æ•°ç»„æˆ–è€…æ˜ å°„ç±»åž‹ï¼ˆä¸”éž Json::Arr/Obj ï¼‰æ—¶æ‰æœ‰ç”¨ï¼Œå…¶ä»–时候å¯ä»¥éšæ„填写,通常用 nullptr 。

æ¯”å¦‚ä½ éœ€è¦æ•°ç»„,但是 Json å†…éƒ¨ä¸æ˜¯æ•°ç»„,就会返回第三个字段的默认值。 Json ä¹Ÿæ˜¯æ•°ç»„ï¼Œä½†æ˜¯å†…éƒ¨åªæœ‰éƒ¨åˆ†å…ƒç´ èƒ½å¤Ÿè½¬æˆä½ éœ€è¦çš„ç±»åž‹ï¼Œé‚£ä¹ˆå…¶ä»–å…ƒç´ ä¼šç”¨ç¬¬å››ä¸ªå‚æ•°çš„默认值填充,ä¿è¯æ•°ç»„长度一致。

ç„¶åŽä½ å¯ä»¥åƒä¸‹é¢è¿™æ ·åœ¨ä¸¤ç§ç±»åž‹ä¹‹é—´æ¥å›žåˆ‡æ¢ï¼š

Json v_data2{ MyData2{} };
std::println("");
v_data2.writef( std::cout );
std::println("");
v_data2["data"]["id"] = 8848;
v_data2["data"]["name"] = "Mount Everest";
v_data2["data"]["active"] = true;
v_data2["data_list"].push_back( v_data2["data"] );
v_data2["name"] = "name_name";
MyData2 d_data2{ v_data2 };
M_EXPECT_TRUE( d_data2.my_data.id == 8848 ); // true
M_EXPECT_TRUE( d_data2.my_data.m_name == "Mount Everest" ); // true
M_EXPECT_TRUE( d_data2.data_list.size() == 1 ); // true
M_EXPECT_TRUE( d_data2.data_list[0].id == 8848 ); // true
M_EXPECT_TRUE( d_data2.data_list[0].m_name == "Mount Everest" ); // true
M_EXPECT_TRUE( d_data2.name == "name_name" ); // true

这里的 M_EXPECT_TRUE 使用的是 vct-test-unit 库,你å¯ä»¥ä¸ç”¨åœ¨æ„。

列表与映射扩展

内容å˜å¾—è¶Šæ¥è¶Šå¤æ‚了,这里作为最åŽä¸€éƒ¨åˆ†ï¼Œå°†ä»‹ç»åˆ—表和映射的实现细节。

æˆ‘ä»¬ä¹‹å‰æåˆ°ï¼ŒåŽŸå…ˆåªæœ‰å…­ç§ JSON 类型和基本ç 9444 ®—术类型是能够直接和 Json 转æ¢çš„,而自定义类型需æä¾›è½¬æ¢å‡½æ•°å’Œæž„造函数。 ä¹Ÿå°±æ˜¯åªæœ‰æ»¡è¶³ json::convertible 概念的类型æ‰èƒ½ç›´æŽ¥è½¬æ¢ã€‚

ä½†æ˜¯ï¼Œåƒ array<int> è¿™ç§æ ‡å‡†åº“æä¾›çš„类型怎么办呢?它很常用,内部的 int æ»¡è¶³äº†è½¬æ¢æ¡ä»¶ï¼Œä½†æ•´ä½“并䏿»¡è¶³ï¼Œåˆæ— æ³•让它æä¾›è½¬æ¢å‡½æ•°å’Œæž„造函数。

因此,本库为 Json æä¾›äº†å››ä¸ªæ¨¡æ¿å‡½æ•°ï¼Œåˆ†åˆ«å¯¹åº” 数组类型->Json å’Œ Json->数组类型 ä»¥åŠ æ˜ å°„ç±»åž‹->Json å’Œ Json->映射类型 的转æ¢ã€‚

什么类型能够用这些模æ¿å‘¢ï¼Ÿé¦–先映射类型的键必须是 std::string 或者å¯ä»¥è½¬æ¢ä¸º std::string 的类型。 最é‡è¦çš„æ˜¯å†…éƒ¨çš„å€¼ç±»åž‹ï¼Œè¦æ±‚是 json::convertible ,因为这些类型能够直接转æ¢ã€‚

这就是为什么会有两个独立的概念 json::convertible_map 和 json::convertible_array 。

数组/映射->Json是ä¸ä¼šé—æ¼ä»»ä½•元素的,因为所有元素都能被 Json 接å—。 但是å之则ä¸ç„¶ï¼ŒJson->数组/映射 å¯èƒ½ä¼šä¸¢å¤±ä¸€äº›å…ƒç´ ï¼Œå› ä¸º Json å¯èƒ½æœ‰å„ç§å¥‡æ€ªçš„æ•°æ®å’Œæ ¼å¼ã€‚

å› æ­¤ï¼Œå¦‚æžœä½ çš„æ•°ç»„å’Œæ˜ å°„ä¸æ˜¯åŸºæœ¬ç±»åž‹é‡Œçš„ Json::Arr å’Œ Json::Obj ï¼Œé‚£ä¹ˆåœ¨è½¬æ¢æ—¶å¿…é¡»æä¾›ä¸¤ä¸ªé»˜è®¤å€¼ï¼š

  1. 完全ä¸åŒ¹é…时返回的默认结果。比如需è¦è½¬æ¢æˆæ•°ç»„,但是 Json å†…éƒ¨ä¸æ˜¯æ•°ç»„,则直接返回此默认值。

  2. 能够匹é…类型,但是局部元素ä¸åŒ¹é…æ—¶å¡«å……çš„å­å…ƒç´ é»˜è®¤å€¼ã€‚æ¯”å¦‚éœ€è¦ vector<int> ,但是 Json 中是 [1, 2, [], 3] ï¼Œä½ éœ€è¦æŒ‡å®šé‡åˆ° [] 这些ä¸åŒ¹é…元素时填充的默认整数。

这也就是为什么 M_JSON_CS_MEM_OR å’Œ M_JSON_CS_MAP_OR å®å®šä¹‰éœ€è¦ä¸¤ä¸ªé»˜è®¤å€¼ã€‚ ä¸è¿‡ï¼Œå¦‚果你转æ¢çš„ç±»åž‹ä¸æ˜¯æ•°ç»„或者映射,最åŽè¿™ä¸ªå­å…ƒç´ é»˜è®¤å€¼å¯ä»¥ä»»æ„å¡«å†™ï¼Œä¸Šé¢æˆ‘们就使用过 nullptr 作为默认值。

此内容在代ç ä¸­å®žé™…对应 to/move 系列函数。

对于基本类型或者自定义类型的数æ®ï¼Œå¯ä»¥åƒä¹‹å‰ä¸€æ ·ç›´æŽ¥è½¬æ¢ï¼š

xxx = val.to<MyData>(); // 或者 move<MyData>()

但如果需è¦è½¬æ¢æˆæ•°ç»„ï¼Œå°±éœ€è¦æ˜¾å¼æŒ‡å®šå­å…ƒç´ é»˜è®¤å€¼ï¼š

// å®žé™…æ¨¡æ¿æœ‰ä¸¤ä¸ªå‚数,第一个是目标类型,第二个是填充å­å…ƒç´ çš„类型
xxx = val.to<std::vector<MyData>, MyData>( MyData{} );
// å¯ä»¥æ ¹æ®å‡½æ•°å‚数自动推导第二个模æ¿å‚æ•°
xxx = val.to<std::vector<MyData>>( MyData{} ); 

第二个模æ¿å‚数默认是 Json::Nul ï¼Œå³ std:::nullptr_t 。如果转æ¢ç›®æ ‡ä¸æ˜¯æ•°ç»„或对象,完全ä¸éœ€è¦æ·»åŠ å®ƒã€‚

注æ„, to/move 在完全ä¸åŒ¹é…æ—¶ç›´æŽ¥æŠ›å‡ºå¼‚å¸¸ï¼Œæ‰€ä»¥æˆ‘ä»¬åªæŒ‡å®šäº†å­å…ƒç´ é»˜è®¤å€¼ã€‚ 而å®å®šä¹‰å®žé™…ç”± to_or å’Œ move_or 实现,因此需è¦ä¸¤ä¸ªé»˜è®¤å€¼ï¼š

// 第二个模æ¿å‚数使用自动推导
xxx = val.to_or<std::vector<MyData>>( std::vector<MyData>{} , MyData{} ); 

最åŽ

以上就是本库的基本使用,虽然自定义类型åºåˆ—åŒ–çš„éƒ¨åˆ†æ¯”è¾ƒå¤æ‚,但你å¯ä»¥è‡ªè¡Œé˜…读文档和æºç ï¼Œæœ¬åº“çš„æºç çš„æœ‰æ•ˆè¡Œæ•°å…¶å®žéžå¸¸å°‘(ä¸è¶³ 2000 行)。 é‡ç‚¹è§‚察 to å’Œ move 的实现,以åŠå¤´æ–‡ä»¶ä¸­çš„å®å®šä¹‰ï¼Œä½ åº”该能很快上手。

如果你å‘现的本库的任何问题,或者å¯ä¼˜åŒ–的地方,欢迎æäº¤ issue 或 PR。


memory des_mix des_num des_str ser_mix ser_num ser_str get_copy add_copy delete

About

A lightweight and efficient C++20 JSON library.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

0