xyjson API 参考手册

本手册提供 xyjson 库的完整 API 参考,作为查询手册之用。

1 核心类介绍

在 xyjson 中定义的类,基本都是对 yyjson 底层 C 结构体指针的封装, 所有类均在 yyjson:: 命名空间(而不是 xyjson::)下定义:

  • Document - 只读 JSON 文档树包装器
  • MutableDocument - 可写 JSON 文档树包装器
  • Value - 只读 JSON 结点值包装器
  • MutableValue - 可写 JSON 结点值包装器
  • ArrayIterator - 只读数组迭代器
  • MutableArrayIterator - 可写数组迭代器
  • ObjectIterator - 只读对象迭代器
  • MutableObjectIterator - 可写对象迭代器

详细的类层次关系请参考:类图

1.1 泛型类型约定

本文档使用以下泛型名称:

  • jsonT - Value 与 MutableValue 类型统称
  • docT - Document 与 MutableDocument 类型统称
  • iteratorT - 四种迭代器类型统称
  • scalarT - 数字与字符串等基本标量类型统称
  • stringT - 字符串,包括 C-Style 的 const char* 与 C++ 的 std::string
  • keyT - 同 stringT ,json 键只能用字符串

整数一般指最常用的 int ,但在用于索引时内部用标准的 size_t ,而 int 也会 自动提升转为 size_t 。yyjson 在存整数时使用的是 uint64_tint64_t ,平 时使用小整数时一般向下转 int

2 操作符重载

2.1 操作符总览表

优先级 操作符 左参类型 右参类型 返回类型 对应方法 功能描述 推荐度
0 ”{}”_xyjson 字符串字面量 Document 构造函数 字符串字面量自定义转换 3
2 a.b 对象 成员 auto   成员访问,不可重载 3
2 it->b iteratorT jsonT 成员方法 auto value 间接成员访问 3
2 json->b jsonT jsonT 成员方法 auto this 间接成员访问 2
2 json[key] jsonT 字符串、整数 jsonT index 常规单层索引 4
2 it++ iteratorT iteratorT next 后缀自增,返回原拷贝 2
2 it– iteratorT iteratorT prev 后缀自减,返回原拷贝 1
2 bool(a) T bool isValid 布尔条件上下文直接判断 4
3 (bool)a T bool isValid 显式转布尔逻辑值 3
3 ++it iteratorT iteratorT& next 前缀自增再返回 4
3 –it iteratorT iteratorT& prev 前缀自减再返回 3
3 !a T bool !isValid 错误判断/无效判断 5
3 (int)json jsonT int toInteger 转整数 3
3 (double)json jsonT double toNumber 转浮点数 3
3 (string)json jsonT std::string toString 转字符串 3
3 ~doc Document MutableDocument mutate 可读写模式转换 4
3 ~doc MutableDocument Document freeze 可读写模式转换 4
3 ~it iteratorT jsonT key 迭代器当前键结点 2
3 *it iteratorT jsonT value 迭代器当前值结点 5
3 *doc docT jsonT root 取根结点 4
3 +json jsonT int toInteger 转整数 3
3 +it iteratorT size_t index 取迭代器当前索引 2
3 -json jsonT std::string toString 转字符串 3
3 -it iteratorT const char* name 取迭代器当前键名 2
5 json / path jsonT 字符串、整数 jsonT pathto 标准链式路径访问 5
5 it / key iteratorT 字符串 jsonT seek 连续快速查找固定键 4
5 doc * b MutableDocument scalarT MutableValue create 创建 JSON 结点 3
5 doc * b MutableDocument jsonT docT MutableValue create 复制 JSON 结点 3
5 doc * b MutableDocument MutableValue&& MutableValue create 移动 JSON 结点 2
5 json * json MutableValue&& MutableValue&& KeyValue tag 绑定键值对 3
5 json * key MutableValue keyT KeyValue tag 值绑定键名 3
5 json % key jsonT 字符串、整数 iteratorT iterator 创建迭代器 4
5 it % key iteratorT 字符串、整数 iteratorT advance 拷贝迭代器重定位 2
6 it + n iteratorT size_t iteratorT advance 拷贝迭代器多步前进 2
7 os « json 输出流 jsonT std::ostream& toString 序列化输出 4
7 doc « input docT 字符串、文件 bool read 读入json解析 4
7 json « b MutableValue scalarT 等类型 MutableValue push 容器尾部追加结点 5
7 it « b iteratorT scalarT 等类型 iteratorT& insert 容器迭代器定点插入 4
7 doc » output docT 字符串、文件 bool write 序列化输出 4
7 json » b jsonT scalartT& bool get 值提取并判断是否成功 4
7 json » json MutableValue MutableValue& MutableValue& pop 数组尾部删除 3
7 json » kv MutableValue KeyValue& MutableValue& pop 对象尾部删除 3
7 it » json iteratorT MutableValue& iteratorT& remove 数组迭代器定点删除 3
7 it » kv iteratorT KeyValue& iteratorT& remove 对象迭代器定点删除 3
9 a < b jsonT jsonT bool less json 有序性定义 3
9 a <= b jsonT jsonT bool less json 有序性定义 2
9 a > b jsonT jsonT bool less json 有序性定义 2
9 a >= b jsonT jsonT bool less json 有序性定义 2
10 a == b T T bool equal 相等性判断 4
10 a != b T T bool !equal 不等性判断 4
11 json & b jsonT scalarT bool isType 类型判断 4
13 json | b jsonT scalarT auto getor 带默认值提取 5
13 json | func jsonT 函数 auto pipe 自定义管道函数转换 4
16 json = json jsonT jsonT jsonT&   自赋值拷贝 5
16 json = b MutableValue scalarT MutableValue& set 基本类型赋值转 json 5
16 a |= json scalarT jsonT scalarT& get 复合提取赋值给原变量 4
16 it += n iteratorT size_t iteratorT& advance 迭代器多步前进 3
16 it %= key iteratorT 字符串、整数 iteratorT& advance 迭代器重定位 3
            以下转调根结点操作  
2 doc[key] docT 字符串、整数 jsonT index 取根结点单层索引 4
3 +doc jsonT int toInteger 取根结点转整数 3
3 -doc jsonT std::string toString 取根结点转字符串 3
5 doc / path docT 字符串、整数 jsonT pathto 根结点路径访问 5
5 doc % key docT 字符串、整数 iteratorT iterator 根结点创建迭代器 4
7 os « doc 输出流 docT os& toString 根结点序列化流输出 4
10 doc == doc docT docT bool equal 根结点相等性判断 4

重要说明

  1. . 操作符 - 不可重载,用于调用成员方法,优先级很高(2),推荐度中等 是因为大部分方法都可用其他操作符重载实现
  2. _xyjson 字面量操作符 - 用户定义字面量,优先级最高(无表达式优先级问题)
  3. 操作符重载不能改变 C++ 固有的优先级和结合律,使用时需注意操作符的正确组合
  4. 操作符优先级为 3 与 16 的结合性从右往左,其他是正常的从左往右

下面将以操作符优先级的顺序介绍每一种操作符重载的用法。因为推荐度、常用、好用等 概念是偏主观的,优先级才是客观的。

2.2 字面量操作符 _xyjson

语法"json_string"_xyjson

功能:从字符串字面量直接构造 Document

  • 参数类型:字符串字面量 (const char*, size_t)
  • 返回值类型yyjson::Document

注意:该字面量操作在 yyjson:: 命名空间定义,需显式引入。其他常规操作符重载虽 也在 yyjson:: 命名空间定义,但可由 ADL(Argument-Dependent Lookup) 规则找到, 不定显式引入命名空间。

using namespace yyjson;
auto doc = R"({"name": "Alice", "age": 30})"_xyjson;

// 等效:显式调用 yyjson::operator""_xyjson
{
    const char* str = R"({"name": "Alice", "age": 30})";
    size_t len = ::strlen(str);
    auto doc = yyjson::operator""_xyjson(str, len);
}

// 等效:使用构造函数
{
    yyjson::Document doc(R"({"name": "Alice", "age": 30})");
}

2.3 成员访问操作符 ->

语法it->method()json->method()

功能:调用成员方法,. 不可重载,但 -> 可重载

  • 左侧参数类型:iteratorT 或 jsonT
  • 右侧参数:方法名
  • 返回值类型:根据方法定义

备注:重载 -> 主要是为了满足迭代器的标准操作,iteratorT::operator-> 返回 jsonT, 而 jsonT::operator-> 返回 this 。客户代码用 -> 调用成员方法是返 回值取决于所调用方法。

yyjson::Document doc(R"({"name": "Alice", "age": 30})");

auto json = doc / "age";
auto iter = doc % "age";
if (json->isInt()) {
    int age = json->toInteger(); // 结果:30
}
if (iter->isInt()) {
    int age = iter->toInteger(); // 结果:30
}

// 等效:使用 . 成员访问操作符
// jsonT 类直接使用 . 访问,iteratorT 类要先解引用
{
    auto json = doc / "age";
    auto iter = doc % "age";
    if (json.isInt()) {
        int age = json.toInteger(); // 结果:30
    }
    if ((*iter).isInt()) {
        int age = (*iter).toInteger(); // 结果:30
    }
}

2.4 索引操作符 []

语法jsonT[key]jsonT[index]

功能:单层索引访问,MutableValue 可自动创建新字段

  • 左侧参数类型jsonT,要求对象或数组类型
  • 右侧参数类型stringT(对象键名)或 size_t(数组索引)
  • 返回值类型jsonT(与左侧参数相同类型)

索引操作符 [] 的优先级与成员访问 . 的优先级相同,可以在 [] 取子元素 后调用方法,这点比路径操作符 / 简洁点。所以如果惯用方法名,可用 [] ,否则 更推荐用 /

注意:索引操作是 O(N) 时间复杂度,不论是用键名索引对象,还是用整数索引数组,都 是线性的。

2.4.1 对象字段访问 index

yyjson::Document doc = R"({"user": {"name": "Alice"}})"_xyjson;
auto user = doc["user"];
auto name = user["name"];

/* use method: index() */
{
    auto name = user.index("name");
}

2.4.2 数组索引访问 index

yyjson::Document doc = R"({"items": [10, 20, 30]})"_xyjson;
auto items = doc["items"];
auto first = items[0];

/* use method: index() */
{
    auto first = items.index(0);
}

2.4.3 可写对象自动添加字段 index

yyjson::MutableDocument mutDoc;
mutDoc["new_field"] = "value";
mutDoc["array"] = "[]";

/* use method: index() */
{
    yyjson::MutableDocument mutDoc;
    mutDoc.root().index("new_field").set("value");
    mutDoc.root().index("array").set("[]");
}

由于 [] 会自动添加不存在字段,尽量避免在 = 右侧使用,意外增加不想增加的键。 使用 [] 有意添加新键时,涉及线性扫描,效率比 << 低,但能避免添加重复键。

2.5 后缀自增 i++

2.5.1 迭代器拷贝前进 next

语法itratorT++

功能:后缀自增,返回原拷贝

  • 参数类型iteratorT
  • 返回值类型iteratorT
yyjson::Document doc = R"({"items": [10, 20, 30]})"_xyjson;
auto iter = doc / "items" % 0;
auto oldIter = iter++;  // 返回原迭代器,然后iter前进

/* use method: next() */
{
    auto iter = doc / "items" % 0;
    auto oldIter = iter;
    iter.next();
}

说明:该操作符涉及拷贝迭代器,推荐尽可能使用前缀自增 ++it

2.6 后缀自减 i--

2.6.1 迭代器拷贝后退 prev

语法itratorT--

功能:后缀自减,返回原拷贝

  • 参数类型iteratorT
  • 返回值类型iteratorT
yyjson::Document doc = R"({"items": [10, 20, 30]})"_xyjson;
auto iter = doc / "items" % 1;
auto oldIter = iter--;  // 返回原迭代器,然后iter后退

/* use method: prev() */
{
    auto iter = doc / "items" % 1;
    auto oldIter = iter;
    iter.prev();
}

说明:该操作符涉及拷贝迭代器,推荐尽可能使用前缀自增 --it

2.7 前缀自增 ++i

2.7.1 迭代器前进 next

语法++iteratorT

功能:前缀自增再返回

  • 参数类型iteratorT&
  • 返回值类型iteratorT&
yyjson::Document doc = R"({"items": [10, 20, 30]})"_xyjson;

auto iter = doc / "items" % 0;
++iter;  // 原迭代器前进并返回

/* use method: next() */
{
    auto iter = doc / "items" % 0;
    iter.next();
}

2.8 前缀自减 --i

2.8.1 迭代器后退 prev

语法--iteratorT

功能:前缀自减再返回

  • 参数类型iteratorT&
  • 返回值类型iteratorT&
yyjson::Document doc = R"({"items": [10, 20, 30]})"_xyjson;

auto iter = doc / "items" % 1;
--iter;  // 原迭代器后退并返回

/* use method: prev() */
{
    auto iter = doc / "items" % 1;
    iter.prev();
}

说明:迭代器的后退功能不是 O(1) 而是 O(N) 操作。

2.9 逻辑非 !

2.9.1 无效值判断 isValid

语法!jsonT!docT!itratorT

功能:错误判断/无效判断

  • 参数类型:jsonT, docT, iteratorT
  • 返回值类型bool
yyjson::Document doc = R"({})"_xyjson;


if (!doc) {
    // 文档无效,可能是解析错误
}
if (!(doc / "nonexistent")) {
    // 路径不存在
}
if (!(doc % "nonexistent")) {
    // 迭代器无效
}

/* use method: isValid() */
if (doc.hasError()) { // 或 !doc.isValid()
    // 文档有错误
}
if (!doc["nonexistent"].isValid()) {
    // 结点无效
}
if (doc.root().iterator("nonexistent").isValid() == false) {
    // 迭代器无效
}

2.10 类型转换 (type)atype(a)

注意:C 风格的类型转换 (type) a 优先级是 3 ,C++ 构造函数风格的类型转换 type(a) 优先级是 2 ,略有微小差异。

Value 类支持向 booldouble, intstd::string 的显式类型转换。 其中 bool 类型转换支持在布尔上下文如条件判断中直接使用,与 ! 是相反的语义, 用于判断各类对象是否有效。而后三者转数字、转整数与转字符串,则根据 json 内容所 做的较复杂的逻辑转换。

两种 Document 类支持在只读与可写类型之间互转(拷贝式),但两种 Value 类不支持 互转。虽然 yyjson 底层指针 yyjson_mut_val* 在表示标量叶结点时的内部实现有时 可强转为 yyjson_val* ,因为前 16 字节意义相同,但在表示容器时两者有显著区别, 故不支持 ValueMutableValue 的互相转换。

2.10.1 布尔转换 bool

语法bool(jsonT)bool(docT)bool(iteratorT)

功能:显式转布尔逻辑值,判断对象有效性。条件语句中可直接使用,或使用双重否 定 !!。另见操作符 !

  • 参数类型:jsonT, docT, iteratorT
  • 返回值类型bool
yyjson::Document doc = R"({"name": "Alice"})"_xyjson;

/* use operator */
if (doc) {
    // 文档有效,解析无错误
}
bool isValid = bool(doc / "name");

/* use method */
if (doc.hasError()) {
    // 文档解析出错
}
bool isValid2 = doc["name"].isValid();

注意:json 转 bool 只判断所引用的结点是否有效(相当于指针判空),但是不管 json 结点的内容是否为 null, false, 0 或空字符串、空数组、空对象等。如果 想判断这类空内容可先转整数再与 0 比较。

yyjson::Document doc = R"({"null":null, "bool":false, "number":0, "string":"", "array":[], "object":{}})"_xyjson;

bool hasNode = false;
// 以下 hasNode 都返回 true
hasNode = (bool)doc["null"];
hasNode = (bool)doc["bool"];
hasNode = (bool)doc["number"];
hasNode = (bool)doc["string"];
hasNode = (bool)doc["array"];
hasNode = (bool)doc["object"];

bool hasValue = true;
// 以下 hasValue 都返回 false
hasValue = (int)doc["null"];
hasValue = (int)doc["bool"];
hasValue = (int)doc["number"];
hasValue = (int)doc["string"];
hasValue = (int)doc["array"];
hasValue = (int)doc["object"];

2.10.2 整数转换 int

语法(int)jsonTint(jsonT)

功能:显式转整数,另见一元操作符 +

  • 参数类型:jsonT
  • 返回值类型int

各类型 json 结点转整数逻辑:

  • null: 0
  • bool: false 0, true 1
  • number: 截断为 int
  • string: 使用 atoi 将字符串转整数,可能只转前缀部分
  • array/object: 取容器大小,即子元素个数
  • 无效结点也返回 0
yyjson::Document doc = R"({"null":null, "bool":true, "number":3.14,
"string":"3.14pi", "array":[null,null], "object":{"x":0, "y":0, "z":0}})"_xyjson;

int iError  = (int)doc["error"];  // 0
int iNull   = (int)doc["null"];   // 0
int iBool   = (int)doc["bool"];   // 1
int iNumber = (int)doc["number"]; // 3
int iString = (int)doc["string"]; // 3
int iArray  = (int)doc["array"];  // 2
int iObject = (int)doc["object"]; // 3

/* use method: toInteger() */
{
    int iError  = doc["error"].toInteger();  // 0
    int iNull   = doc["null"].toInteger();   // 0
    int iBool   = doc["bool"].toInteger();   // 1
    int iNumber = doc["number"].toInteger(); // 3
    int iString = doc["string"].toInteger(); // 3
    int iArray  = doc["array"].toInteger();  // 2
    int iObject = doc["object"].toInteger(); // 3
}

2.10.3 实数转换 double

语法(double)jsonT 或 double(jsonT)

功能:转浮点实数,大整数可能丢失精度,非数字类型结点返回 0.0

  • 参数类型jsonT
  • 返回值类型double

注意:转实数的 toNUmber() 方法只针对 json 的 Number 类型,不像转整数的 toInteger() 方法试图解释所有 json 类型在整数上下文的意义。

yyjson::Document doc = R"({"null":null, "bool":true, "number":3.14, "int":3,
"string":"3.14pi", "array":[null,null], "object":{"x":0, "y":0, "z":0}})"_xyjson;

double fError  = (double)doc["error"];  // 0.0
double fNull   = (double)doc["null"];   // 0.0
double fBool   = (double)doc["bool"];   // 0.0
double fNumber = (double)doc["number"]; // 3.14
double fInt    = (double)doc["int"];    // 3.0
double fString = (double)doc["string"]; // 0.0
double fArray  = (double)doc["array"];  // 0.0
double fObject = (double)doc["object"]; // 0.0

/* use method: toNumber() */
{
    double fError  = doc["error"].toNumber();  // 0.0
    double fNull   = doc["null"].toNumber();   // 0.0
    double fBool   = doc["bool"].toNumber();   // 0.0
    double fNumber = doc["number"].toNumber(); // 3.14
    double fInt    = doc["int"].toNumber();    // 3.0
    double fString = doc["string"].toNumber(); // 0.0
    double fArray  = doc["array"].toNumber();  // 0.0
    double fObject = doc["object"].toNumber(); // 0.0
}

2.10.4 字符串转换 std::string

语法(std::string)jsonTstd::string(jsonT)

功能:转字符串,另见操作符 -

  • 参数类型jsonT
  • 返回值类型std::string
yyjson::Document doc = R"({"null":null, "bool":true, "number":3.14,
"string":"3.14pi", "array":[null,null], "object":{"x":0, "y":0, "z":0}})"_xyjson;

std::string strError  = (std::string)doc["error"];  // ""
std::string strNull   = (std::string)doc["null"];   // null
std::string strBool   = (std::string)doc["bool"];   // true
std::string strNumber = (std::string)doc["number"]; // 3.14
std::string strString = (std::string)doc["string"]; // 3.14pi
std::string strArray  = (std::string)doc["array"];  // [null,null]
std::string strObject = (std::string)doc["object"]; // {"x":0,"y":0,"z":0}
std::string strRoot   = (std::string)doc.root();

/* use method: toString() */
{
    std::string strError  = doc["error"].toString();  // ""
    std::string strNull   = doc["null"].toString();   // null
    std::string strBool   = doc["bool"].toString();   // true
    std::string strNumber = doc["number"].toString(); // 3.14
    std::string strString = doc["string"].toString(); // 3.14pi
    std::string strArray  = doc["array"].toString();  // [null,null]
    std::string strObject = doc["object"].toString(); // {"x":0,"y":0,"z":0}
    std::string strRoot   = doc.root().toString();
}

转字符串其实就相当于 json 序列化,以字符串方式表达任意 json 类型的结点。除了字 符串结点,序列化时会加引号,而转字符串只返回字符串内容值。但 toString() 方法 能接收可选参数 true ,表示真序列化,字符串类型会加引号,且容器类型会加缩进, 适合打印输出给人工阅读。例如:

yyjson::Document doc = R"({"null":null, "bool":true, "number":3.14,
"string":"3.14pi", "array":[null,null], "object":{"x":0, "y":0, "z":0}})"_xyjson;

std::string strString = doc["string"].toString(true); // "3.14pi"
std::string strArray  = doc["array"].toString(true);
std::string strObject = doc["object"].toString(true);

最后一条语句输出格式大约是:

{
    "x": 0,
    "y": 0,
    "z": 0
}

2.10.5 只读文档转可写 MutableDocument

语法(MutableDocument)docMutableDocument(doc)

功能:只读 json 文档树转可写文档,另见操作符 ~

  • 参数类型Document
  • 返回值类型MutableDocument
yyjson::Document doc("{}");
yyjson::MutableDocument mutDoc(doc);

/* use method: mutate() */
{
    yyjson::MutableDocument mutDoc = doc.mutate();
}

2.10.6 可写文档转只读 Document

语法(Document)docDocument(doc)

功能:可写 json 文档树转只读文档,另见操作符 ~

  • 参数类型MutableDocument
  • 返回值类型Document
yyjson::MutableDocument mutDoc("{}");
yyjson::Document doc(mutDoc);

/* use method: freeze() */
{
    yyjson::Document doc = mutDoc.freeze();
}

2.11 按位非 ~

重载 ~ 操作符用于文档类在只读与可写类型之间互转; 也用于迭代器取当前键结点,为与 * 取键结点相对应。

2.11.1 只读文档转可写 mutabte

语法~doc

功能:只读 json 文档树转可写文档

  • 参数类型Document
  • 返回值类型MutableDocument
yyjson::Document doc("{}");
yyjson::MutableDocument mutDoc = ~doc;

/* use method: mutate() */
{
    yyjson::MutableDocument mutDoc = doc.mutate();
}

2.11.2 可写文档转只读 freeze

语法~doc

功能:可写 json 文档树转只读文档

  • 参数类型MutableDocument
  • 返回值类型Document
yyjson::MutableDocument mutDoc("{}");
yyjson::Document doc = ~mutDoc;

/* use method: freeze() */
{
    yyjson::Document doc = mutDoc.freeze();
}

2.11.3 迭代器取键结点 key

语法~iteratorT

功能:获取迭代器当前键结点

  • 参数类型iteratorT
  • 返回值类型jsonT
yyjson::Document doc = R"({"user": {"name": "Alice", "age": 30}})"_xyjson;
for (auto iter = doc / "user" % ""; iter; ++iter) {
    auto keyNode = ~iter;  // 获取键结点
    auto valueNode = *iter;  // 获取值结点
    auto keyName = -iter;  // 获取键名
}

/* use method: key() */
for (auto iter = doc / "user" % ""; iter; ++iter) {
    auto keyNode = iter.key();  // 获取键结点
    auto valueNode = iter.value();  // 获取值结点
    auto keyName = iter.name();  // 获取键名
}

说明:该操作符较少使用,通常直接访问键名和值结点即可满足需求。

2.12 一元解引用 *

重载 * 操作符用于迭代器取当前值结点;也用于文档类取根结点,有很多操作符作用 于文档时其实是转为作用于其根结点。

2.12.1 迭代器取值结点 value

语法*iteratorT

功能:迭代器当前值结点

  • 参数类型iteratorT
  • 返回值类型jsonT
yyjson::Document doc = R"({"items": [10, 20, 30]})"_xyjson;
int sum = 0;
for (auto iter = doc / "items" % 0; iter; ++iter) {
    auto value = *iter;  // 获取当前元素值
    sum += value | 0;
}

/* use method: value() */
sum = 0;
for (auto iter = doc / "items" % 0; iter; ++iter) {
    auto value = iter.value();  // 获取当前元素值
    sum += value | 0;
}

2.12.2 文档取根结点 root

语法*docT

功能:从 Document 获取根结点

  • 参数类型docT
  • 返回值类型jsonT(Document 对应的 Value 类型)
yyjson::Document doc = R"({"name": "Alice"})"_xyjson;
auto root = *doc;

yyjson::MutableDocument mutDoc(doc);
auto mutRoot = *mutDoc;

/* use method: root() */
{
    auto root = doc.root();
    auto mutRoot = mutDoc.root();
}

2.13 一元正号 +

重载一元 + 操作符用于 json 结点类时转整数,用于迭代器类时取当前索引。尽量在 整数上下文中表达合适的意义。

2.13.1 Json 类型转整数 toInteger

语法+jsonT

功能:为各种 json 类型解释在整数上下文的意义

  • 参数类型jsonT
  • 返回值类型int

各类型 json 结点转整数逻辑:

  • null: 0
  • bool: false 0, true 1
  • number: 截断为 int
  • string: 使用 atoi 将字符串转整数,可能只转前缀部分
  • array/object: 取容器大小,即子元素个数
  • 无效结点也返回 0
yyjson::Document doc = R"({"null":null, "bool":true, "number":3.14,
"string":"3.14pi", "array":[null,null], "object":{"x":0, "y":0, "z":0}})"_xyjson;

int iError  = +doc["error"];  // 0
int iNull   = +doc["null"];   // 0
int iBool   = +doc["bool"];   // 1
int iNumber = +doc["number"]; // 3
int iString = +doc["string"]; // 3
int iArray  = +doc["array"];  // 2
int iObject = +doc["object"]; // 3

/* use method: toInteger() */
{
    int iError  = doc["error"].toInteger();  // 0
    int iNull   = doc["null"].toInteger();   // 0
    int iBool   = doc["bool"].toInteger();   // 1
    int iNumber = doc["number"].toInteger(); // 3
    int iString = doc["string"].toInteger(); // 3
    int iArray  = doc["array"].toInteger();  // 2
    int iObject = doc["object"].toInteger(); // 3
}

2.13.2 迭代器取当前索引 index

语法+iteratorT

功能:取迭代器当前索引

  • 参数类型iteratorT
  • 返回值类型size_t
yyjson::Document doc = R"({"items": [10, 20, 30]})"_xyjson;
size_t lastIndex = 0;
int sum = 0;
for (auto iter = doc / "items" % 0; iter; ++iter) {
    size_t idx = +iter;
    auto val = *iter;
    lastIndex = idx;
    sum += val | 0;
}

/* use method: index() */
lastIndex = 0;
sum = 0;
for (auto iter = doc / "items" % 0; iter; ++iter) {
    size_t idx = iter.index();
    auto val = iter.value();
    lastIndex = idx;
    sum += val | 0;
}

2.14 一元负号 -

重载一元 - 操作符用于 json 结点类时转字符串,用于迭代器类时取当前键名。

2.14.1 Json 类型转字符串 toString

语法-jsonT

功能:Json 结点转字符串

  • 参数类型jsonTdocT
  • 返回值类型std::string
yyjson::Document doc = R"({"null":null, "bool":true, "number":3.14,
"string":"3.14pi", "array":[null,null], "object":{"x":0, "y":0, "z":0}})"_xyjson;

std::string strError  = -doc["error"];  // ""
std::string strNull   = -doc["null"];   // null
std::string strBool   = -doc["bool"];   // true
std::string strNumber = -doc["number"]; // 3.14
std::string strString = -doc["string"]; // 3.14pi
std::string strArray  = -doc["array"];  // [null,null]
std::string strObject = -doc["object"]; // {"x":0,"y":0,"z":0}
std::string strRoot   = -doc.root();

/* use method: toString() */
{
    std::string strError  = doc["error"].toString();  // ""
    std::string strNull   = doc["null"].toString();   // null
    std::string strBool   = doc["bool"].toString();   // true
    std::string strNumber = doc["number"].toString(); // 3.14
    std::string strString = doc["string"].toString(); // 3.14pi
    std::string strArray  = doc["array"].toString();  // [null,null]
    std::string strObject = doc["object"].toString(); // {"x":0,"y":0,"z":0}
    std::string strRoot   = doc.root().toString();
}

另注:- 相当于 json 单行序列化,但对于字符串结点不加引号;方法 toString() 能接收可选参数 true,表示真序列化,字符串类型会加引号,且容器类型会加缩进。

2.15 迭代器取当前键名 name

语法-iteratorT

功能:取迭代器当前键名

  • 参数类型iteratorT
  • 返回值类型const char*
yyjson::Document doc = R"({"user": {"name": "Alice", "age": 30}})"_xyjson;
int count = 0;
for (auto iter = doc / "user" % ""; iter; ++iter) {
    const char* keyName = -iter;
    auto valNode = *iter;
    count++;
}

/* use method: name() */
count = 0;
for (auto iter = doc / "user" % ""; iter; ++iter) {
    const char* keyName = iter.name();
    auto valNode = iter.value();
    count++;
}

2.16 除法运算 /

重载操作符 / 是 xyjson 库的核心特色,在大部分情况下可平替 [] 索引操作,且 衍生支持更高级的扩展功能。

2.16.1 链式路径访问 pathto

语法jsonT / keyjsonT / index

功能:类似 [] 索引的单层访问与线性查找,更方便链式逐级访问

  • 左侧参数类型jsonT,要求对象或数组类型
  • 右侧参数类型stringT(对象键名)或 size_t(数组索引)
  • 返回值类型jsonT(与左侧参数相同类型)

参数类型不匹配,或键名不存在,或数组索引越界时,返回无效值。即使可写 json 对象 路径访问不存在键时返回无效值,不会自动添加新键。

yyjson::Document doc = R"({"user": {"name": "Alice", "age": 30},
    "items": [10, 20, 30]})"_xyjson;
auto name = *doc / "user" / "name";
auto item = *doc / "items" / 0;

/* use method: pathto() */
{
    auto name = doc.root().pathto("user").pathto("name");
    auto item = doc.root().pathto("items").index(0);
}

路径操作符 / 优先级比 . 的成员访问优先级低,如果想对返回值调用方法,需要加 括号提升优先级,或者可改用 []

yyjson::Document doc = R"({"user": {"name": "Alice", "age": 30}})"_xyjson;
int age = (*doc / "user" / "age").toInteger();

/* use operator[] */
{
    int age = doc.root()["user"]["age"].toInteger();
}

/* use method: pathto() */
{
    int age = doc.root().pathto("user").pathto("age").toInteger();
}

说明: 操作符 / 也可直接用于 Document 类,但 pathto 只是 Value 类的方法。

2.16.2 JSON Pointer 路径访问 pathto

语法jsonT / "/json/pointer/path"

功能:支持标准 JSON Pointer 语法

  • 左侧参数类型jsonT
  • 右侧参数类型stringT(JSON Pointer 格式路径)
  • 返回值类型jsonT

注意:右侧参数需要遵循 JSON Pointer 标准。尤其要以 / 字符开头,否则认为是简 单的单层键名查找。

yyjson::Document doc = R"({"user": {"name": "Alice", "age": 30}})"_xyjson;
auto node = *doc / "/user/name";

/* use method: pathto() */
{
    auto node = doc.root().pathto("/user/name");
}

2.16.3 对象迭代器顺序查找 seek

语法iteratorT / key

功能:对象迭代器连续快速查找固定键

  • 左侧参数类型iteratorT(限对象迭代器)
  • 右侧参数类型stringT
  • 返回值类型jsonT

当连续查找的键名顺序与 json 实际所存的键序相同时,只涉及一遍线性查找,而不是每 个键都一次线性查找。所以在最好情况下,每次查找键是 O(1) 时间复杂度。当键名不 存在或乱序时,会退化为 O(N) 查找。

yyjson::Document doc = R"({"config": {"theme": "dark", "lang": "zh"}})"_xyjson;
auto iter = doc / "config" % "";
auto theme = iter / "theme";  // 第一次查找
auto lang = iter / "lang";    // 继续在相同对象中查找

/* use method: seek() */
{
    auto iter = doc / "config" % "";
    auto theme = iter.seek("theme");
    auto lang = iter.seek("lang");
}

2.17 乘法运算 *

常规的乘法 */ 的逆运算。在 xyjson 已经用 / 表示按路径查找 json 结点, 显然对于从头构建的 json 来说,要能用 / 查找到的前提就是先在 json 所在内存池 中创建结点,所以重载 * 用来表达这个功能。

很多时候不需要主动使用 * 操作符,因为往 json 容器中添加结点的操作符会隐含自 动先创建结点。理解 * 操作揭示了内部实现方法。

2.17.1 文档创建新结点 create

语法MutableDocument * value

功能:创建 JSON 结点,支持基本类型和类型常量

  • 左侧参数类型MutableDocument
  • 右侧参数类型scalarT 或特殊参数(如 “[]”, “{}”)
  • 返回值类型MutableValue

当右侧参数是字符串字面量时,创建字符串类型的 json 结点是引用字面量,不发生字符 串拷贝。且支持特殊字面 "[]""{}" 分别表示创建空数组与空对象结点。

新创建建的 json 结点使用左参数文档的内存池,与该文档具有相同的生命周期,但不会 自动挂载到该文档的根结点或其他子树结点,属于独立结点,须由用户再决定如何处理新 结点,是否要添加到文档已有的某个容器结点中。

yyjson::MutableDocument mutDoc;
auto intNode = mutDoc * 314;
auto strNode = mutDoc * "value";
auto newArray = mutDoc * "[]";
auto newObject = mutDoc * "{}";

/* use method: create() */
{
    auto intNode = mutDoc.create(314);
    auto strNode = mutDoc.create("new_value");
    auto newArray = mutDoc.create("[]");
    auto newObject = mutDoc.create("{}");
}

2.17.2 复制结点 create

语法MutableDocument * jsonTMutableDocument * docT

功能:复制已有的 json 结点,或文档的根结点

  • 左侧参数类型MutableDocument
  • 右侧参数类型jsonTdocT
  • 返回值类型MutableValue

复制结点不会影响右侧参数的结点,如果是容器结点将深度复制。复制结点与新建结点一 样使用目标文档的内存池,但不会自动挂载到根结点或子树结点。

yyjson::MutableDocument dstDoc;
yyjson::MutableDocument srcDocMut(R"({"name": "Alice", "age": 30})");
yyjson::Document srcDoc(R"({"name": "Alice", "age": 30})");

auto copyName = dstDoc * (srcDoc / "name");
auto copyAge = dstDoc * (srcDocMut / "age");
auto copyDoc = dstDoc * srcDoc;
auto copyDocMut = dstDoc * srcDocMut;

/* use method: create() */
{
    auto copyName = dstDoc.create(srcDoc / "name");
    auto copyAge = dstDoc.create(srcDocMut / "age");
    auto copyDoc = dstDoc.create(srcDoc);
    auto copyDocMut = dstDoc.create(srcDocMut);
}

2.17.3 移动结点 create

语法MutableDocument * MutableValue

功能:移动一个右值语义的 json 结点代理值

  • 左侧参数类型MutableDocument
  • 右侧参数类型MutableValue&&
  • 返回值类型MutableValue

与复制结点类似也返回一个 MutableValue 新值,但当右侧参数是 MutableValue&& 时,移动后原值失效。原来被移动的 MutableValue 必须与源于 MutableDocument 使用相同的内存池,否则移动后会出现内存问题,生命周期不一致。

另要注意,用该方法的移动结点,并不会真实影响底层 json 树的结点关系,因为 MutableValue 类只相当于底层结点的代理指针。它只强调指针代理权的转交。所以并 不能只靠该操作来真正移动原来某个 json 容器内的结点,需要先将结点从原容器中删除 ,或将新建的独立结点用移动的方式添加到其他容器的某个位置,避免将同一个底层结点 重复添加到不同的容器中,造成所有权混乱,或者避免将新建结点再次不必要的重复复制 。

移动结点不会在文档的内存池中创建新结点,仍用 create 方法命名主要是内部实现中 上层模板方法转调底层方法时统一命名比较方便,只用参数重载表达语义的差异。

yyjson::MutableDocument mutDoc(R"(["Alice", 30])");

auto node = mutDoc * 2.5;     // node 有效
auto level = mutDoc * std::move(node); // node 失效
mutDoc.root() << std::move(level);   // level 失效
//^ 结果:["Alice",30,2.5]

// 直接用链式表达式,临时值是纯右值,属移动语义
*mutDoc << mutDoc * true; // 结果:["Alice",30,2.5,true]

// 添加数组可以更简洁点,会自动调用 create
*mutDoc << 100 << 200; // 结果:["Alice",30,2.5,true,100,200]

2.17.4 可写结点绑定键值对 tag

语法MutableValue * MutableValue

功能:将分别表示键与值的两个结点绑定在一起,支持联用创建结点的链式连乘。

  • 左侧参数类型MutableValue&&
  • 右侧参数类型MutableValue&&
  • 返回值类型KeyValue

键值对绑定后生成一个中间类型 KeyValue ,它的主要只用是将来用于插入 json 对象。 为了避免将 MutableValue 所引用的结点重复插入对象造成的错误,要求移动语义,绑 定后原来的两个值失效。如果使用链式连乘,绑定的两个值都是用 * 创建的临时值, 那它们正好是右值,但如果保存了中间变量成了左值,就要显式使用 std::move 才可 以绑定。

yyjson::MutableDocument mutDoc("{}");
auto intNode = mutDoc * 314;
auto keyNode = mutDoc * "key";
auto kv = std::move(keyNode) * std::move(intNode);
mutDoc.root() << std::move(kv); // 结果:{"key":314}

// 使用连乘临时值,一元 `*` 表示 root()
{
    yyjson::MutableDocument mutDoc("{}");
    *mutDoc << (mutDoc * "key") * (mutDoc * 314);
}

2.17.5 可写结点绑定键名 tag

语法MutableValue * keykey * MutableValue

功能:为可写结点绑定键名,支持联用创建结点的链式连乘。

  • 左侧参数类型MutableValue&&
  • 右侧参数类型stringT 可用于键的字符串类型
  • 返回值类型KeyValue

MutableValue 与一个字符串绑定时,字符串会自动先创建键结点,再绑定为键值对 。这时的 * 支持交换律,字符串可写在左边,更符合键值对阅读顺序。但使用对应的 方法名时,字符串参数只能写在右边的括号内。

yyjson::MutableDocument mutDoc;
auto keyValue = mutDoc * "value" * "key";
auto kvConfig = "config" * (mutDoc * "{}");

/* use method: tag() */
{
    auto keyValue = mutDoc.create("value").tag("key");
    auto kvConfig = mutDoc.create("{}").tag("config");
}

// 将绑定的结果插入根对象,生成: {"key":"value","config":{}}
mutDoc.root() << std::move(keyValue) << std::move(kvConfig);

// 连乘直接插入,一元 `*` 表示 root()
{
    yyjson::MutableDocument mutDoc;
    *mutDoc << mutDoc * "value" * "key" << "config" * (mutDoc * "{}");
}

2.18 取模运算 %

操作符 %/ 相关,故 xyjson 重载 % 用于创建迭代器,用另一方式来查找子 结点,相当于是 / 路径查找的变体。

2.18.1 数组迭代器创建 iterator

语法jsonT % size_t

功能:创建数组迭代器,可以指定起始索引,最常用的 0 表示从头开始迭代。

  • 左侧参数类型jsonT(数组)
  • 右侧参数类型size_t(起始索引)
  • 返回值类型ArrayIteratorMutableArrayIterator
yyjson::Document doc = R"({"items": [10, 20, 30]})"_xyjson;
int sum = 0;
for (auto iter = doc / "items" % 0; iter; ++iter) {
    sum += *iter | 0;
}

/* use method: iterator() */
sum = 0;
for (auto iter = doc["items"].iterator(0); iter; ++iter) {
    sum += *iter | 0;
}

2.18.2 对象迭代器创建 iterator

语法jsonT % key

功能:创建对象迭代器,可以指定起始索引,最常用的 "" 表示从头开始迭代,遍 历所有键值对。

  • 左侧参数类型jsonT(对象)
  • 右侧参数类型const char*(C 字符串表示的键名)
  • 返回值类型ObjectIteratorMutableObjectIterator

因为从特定键名开始迭代的需求场景比较罕见,所以右侧参数暂时只支持 const char* 类型的字符串,简单实现。

yyjson::Document doc = R"({"user": {"name": "Alice", "age": 30}})"_xyjson;
int count = 0;
for (auto iter = doc / "user" % ""; iter; ++iter) {
    count++;
}

/* use method: iterator() */
count = 0;
for (auto iter = doc["user"].iterator(""); iter; ++iter) {
    count++;
}

2.18.3 迭代器重定位 advance

语法iteratorT % target

功能:迭代器拷贝重定位,另见不拷贝重定位自身的操作符 %=

  • 左侧参数类型iteratorT&
  • 右侧参数类型intconst char*
  • 返回值类型iteratorT&

迭代器用 % 重定位时会先重置到开始位置,保证用相同的参数重定位时与直接以该参 数创建迭代器时得到相同的迭代器状态。重载该操作符主要为符号语义一致性,实用场景 可能并不多,而且 % 会拷贝新值,效率上也不如 %=

yyjson::Document doc = R"({"user": {"name": "Alice", "age": 30},
    "items": [10, 20, 30]})"_xyjson;
auto itArray = doc / "items" % 1; // 创建迭代器初始位置 1
auto itArrayCopy = itArray % 3;  // 重定位到索引3的元素

auto itObject = *doc % "items"; // 创建迭代器初始位置 "items"
auto itObjectCopy = itObject % "user"; // 重定位到 "user" 位置

/* use method: advance() */
{
    auto itArray = doc["items"].iterator(1);
    auto itArrayCopy = itArray;
    itArrayCopy.begin().advance(3);
    
    auto itObject = doc.root().iterator("items");
    auto itObjectCopy = itObject;
    itObjectCopy.begin().advance("user");
}

2.19 加法运算 +

目前只有迭代器支持加法操作 + ,这属性标准迭代器语义重载,不算特殊重载。

2.19.1 迭代器多步前进 advance

语法iteratorT + n

功能:拷贝迭代器多步前进,另见不拷贝的前进操作符 +=

  • 左侧参数类型iteratorT
  • 右侧参数类型size_t
  • 返回值类型iteratorT

该操作符返回新的迭代器,原迭代器不变。另外由于 yyjson 的迭代器不支持随机定位, it + n 是通过循环 ++ 操作实现的,因而是 O(N) 时间复杂度。

yyjson::Document doc = R"({"items": [10, 20, 30, 40, 50]})"_xyjson;
auto iter = doc / "items" % 0;
auto iterCopy = iter + 3;  // 前进3步,指向第4个元素

/* use method: advance() */
{
    auto iterCopy = iter;
    iterCopy.advance(3);
}

2.20 左移运算 <<

这可能是 C++ 中应用最广泛的重载了,标准库带头重载为流式插入。取其象形,xyjson 也为各个类重载了 << 操作符,用于流式输出,或向 json 容器以至整体文档的输入。

2.20.1 Json 标准流输出 toString

语法std::ostream << jsonT

功能:Json 结点序列化输出到流

  • 左侧参数类型std::ostream&
  • 右侧参数类型jsonT
  • 返回值类型std::ostream& 原左参引用
yyjson::Document doc = R"({"name": "Alice"})"_xyjson;
std::cout << doc / "name" << std::endl;
std::ostringstream oss;
oss << doc / "name";

/* use method: toString() */
{
    std::cout << (doc / "name").toString() << std::endl;
    std::ostringstream oss;
    oss << (doc / "name").toString();
}

2.20.2 读入 Json 字符串 read

语法docT << input

功能:读入并解析 Json 字符串

  • 左侧参数类型docT
  • 右侧参数类型stringT
  • 返回值类型bool(解析是否成功)
yyjson::Document doc;
bool success = doc << R"({"name": "Alice"})";

/* use method: read() */
{
    yyjson::Document doc;
    bool success = doc.read(R"({"name": "Alice"})");
}

// 也可直接用构造函数
{
    yyjson::Document doc(R"({"name": "Alice"})");
}

说明: 如果 Document 原来已经管理一个底层文档树,读入操作将会先释放原有的文档树,替换 为新文档树。使用构造函数则不涉及释放再重建文档树。

2.20.3 读入 Json 文件解析 read

语法docT << input

功能:读取 Json 文件并解析,可能替换原有底层文档树

  • 左侧参数类型docT
  • 右侧参数类型std::istream&FILE*
  • 返回值类型bool(解析是否成功)
yyjson::Document doc;
std::ifstream file("/tmp/data.json");
bool success = doc << file;

/* use method: read() */
{
    yyjson::Document doc;
    std::ifstream file("/tmp/data.json");
    bool success = doc.read(file);
}

// 另有 readFile() 方法接收文件名参数
{
    yyjson::Document doc;
    bool success = doc.readFile("/tmp/data.json");
}

2.20.4 数组追加元素 push

语法MutableValue << value

功能:数组尾部追加元素

  • 左侧参数类型MutableValue(数组)
  • 右侧参数类型:各种能用 MutableDocument 创建结点的参数类型
  • 返回值类型MutableValue& 原左参引用
yyjson::MutableDocument mutDoc;
mutDoc["items"] = "[]";
mutDoc / "items" << 1 << "two" << 3.14;
//^ 结果:{"items":[1,"two",3.14]}

/* use method: push() */
{
    yyjson::MutableDocument mutDoc;
    mutDoc["items"] = "[]";
    mutDoc["items"].push(1).push("two").push(3.14);
}

// push 是 json 容器通用方法,数组也可用 append()
{
    yyjson::MutableDocument mutDoc;
    mutDoc["items"] = "[]";
    mutDoc["items"].append(1).append("two").append(3.14);
}

2.20.5 对象插入键值对 push

语法MutableValue << key << valueMutableValue << KeyValue&&

功能:对象插入键值对(也是尾部追加式)

  • 左侧参数类型MutableValue(对象)
  • 右侧参数类型:各种能用 MutableDocument创建结点的参数类型
  • 返回值类型MutableValue& 原左参引用
yyjson::MutableDocument mutDoc;
auto root = *mutDoc;
root << "key1" << "value1" << "key2" << 42;

/* use method: push() */
{
    yyjson::MutableDocument mutDoc;
    auto root = *mutDoc;
    root.push("key1").push("value1").push("key2").push(42);
}

// 也可用 add() 方法接收两个参数
{
    yyjson::MutableDocument mutDoc;
    auto root = *mutDoc;
    root.add("key1", "value1").add("key2", 42);
}

对象要支持这样的链式交替插入键与值,需要额外维护一个内部状态,是有一定代价的。 如果定义了条件编译宏 XYJSON_DISABLE_CHAINED_INPUT,则该操作方法不可用,默认 是可用的。

另外,对象的 << 操作与 add 方法也支持接收一个打包了键值对的 KeyValue 参数。需要右值 KeyValue ,通过 * 连乘创建的 KeyValue 临时值是右值,可直 接与 << 联用。在禁用了交换插入键与值的情况下可用该操作方式。或者从其他对象删 除的键值对也可保存为 KeyValue ,再用 <<add 方法再插入到其他地方。

yyjson::MutableDocument mutDoc;
*mutDoc << mutDoc * "Alice" * "name" << "age" * (mutDoc * 25);
//^ 结果:{"name":"Alice","age":25}

// 删除结点
yyjson::KeyValue age, name;
*mutDoc >> age >> name;

// 换个顺序重新插入
*mutDoc << std::move(age) << std::move(name);
//^ 结果:{"age":25,"name":"Alice"}

2.20.6 迭代器定点插入 insert

语法iteratorT << value

功能:在可写数组或对象迭代器指定位置插入元素

  • 左侧参数类型iteratorT
  • 右侧参数类型:各种类型,与支持 MutableValue << 的参数类型一样
  • 返回值类型iteratorT& 原左参引用

操作符 << 作用于 MutableValue 时,是在数组或对象末尾添加元素的,作用于可写 迭代器时,可在当前位置之前插入元素。它们所支持的参数意义是一样。

但是迭代器的操作符 << 与它所对应的 insert 方法有点微小差异。调用 insert 方法后迭代器状态指向新插入的元素,也即原来的当前元素之前。如果支持链式调用 it.insert(x).insert(y) 将会往前插入,结果是 y -> x -> 原来的当前元素 。而 链式操作符 << 直觉上应该保留顺序,批量将一些元素插入当前位置(之前),结果是 x -> y -> 原来的当前元素 。因此,操作符 << 在调用 insert 方法后,还会自 动前进一步。

yyjson::MutableDocument mutDoc;
mutDoc["items"] = "[]";
auto iter = mutDoc / "items" % 0;
iter << 10 << 20 << 30;

/* use method: insert() */
{
    yyjson::MutableDocument mutDoc;
    mutDoc["items"] = "[]";
    auto iter = mutDoc / "items" % 0;
    iter.insert(10); ++iter;
    iter.insert(20); ++iter;
    iter.insert(30); ++iter;
}

用对象迭代器插入元素,默认也支持链式交替插入键与值,当用 XYJSON_DISABLE_CHAINED_INPUT 条件编译宏禁用该功能时,可以插入一个 KeyValue 右值参数,另外 insert 方法也支持直接传入键与值两个参数。

yyjson::MutableDocument mutDoc;
mutDoc["user"] = "{}";
auto iter = mutDoc / "user" % "";
iter << "name" << "Alice" << "age" * (mutDoc * 25);

/* use method: insert() */
{
    yyjson::MutableDocument mutDoc;
    mutDoc["user"] = "{}";
    auto iter = mutDoc / "user" % "";
    iter.insert("name"); iter.insert("Alice"); ++iter;
    iter.insert("age", 25); ++iter;
}

注意,操作符 <<insert 方法的另一个差别是返回值不同。操作符 << 返回迭 代器引用本身以支持链式操作。而 insert 方法返回 bool 值表示插入操作成功与否 。在插入数组时,一般都会成功。在交替插入对象时,先插入键时返回 false ,再插 入配对的值时返回 true 。在接收两个键与值参数,或一个键值对 KeyValue 参数, 如果键不是字符串也会返回 false 表示插入失败。

2.21 右移运算 >>

xyjson 重载了 >> 操作,基本满足 << 操作的逆运算。但是不支持标准输入流在 >> 左侧,只支持自定义类在左侧。

2.21.1 文档序列化字符串 write

语法docT >> output

功能:将文档序列化为字符串

  • 左侧参数类型docT
  • 右侧参数类型std::string
  • 返回值类型bool(是否成功)

<<read 方法不一样的是,>>write 的目标只能是 std::string 这种拥有字符串所有权的字符串类,不能是 const char*

yyjson::Document doc = R"({"name": "Alice"})"_xyjson;
std::string str;
doc >> str;

/* use method: write() */
{
    std::string str;
    doc.write(str);
}

2.21.2 文档序列化写入文件

语法docT >> output

功能:将文档序列化直接写入文件

  • 左侧参数类型docT
  • 右侧参数类型std::ostream&FILE*
  • 返回值类型bool(是否成功)
yyjson::Document doc = R"({"name": "Alice"})"_xyjson;
std::ofstream file("/tmp/output.json");
bool success = doc >> file;

/* use method: write() */
{
    std::ofstream file("/tmp/output.json");
    bool success = doc.write(file);
}

// 另有 writeFile() 方法接收文件名参数
{
    bool success = doc.writeFile("/tmp/output.json");
}

写入字符串基本是成功的,除非内存不足。写入文件就有更多场景可能失败了,比如文件 未打开,没有写入权限等。

2.21.3 安全值提取 get

语法jsonT >> target

功能:从 Json 标量叶结点提取值并判断是否成功

  • 左侧参数类型jsonT
  • 右侧参数类型scalarT& 或底层指针类型
  • 返回值类型bool

该操作主要用于 json 叶结点,例如存储着一个标量数字或字符串。如果该 json 的类型 与 >> 右侧参数匹配,就会将 json 结点的内容提取存到右侧参数中,并返回 bool 表示该操作是否成功。该操作适用于需要显式判断提取是否成功的场景,如果希望在失败 时能回滚一个默认值时,使用 | 更为常见。

yyjson::Document doc(R"({"name": "Alice", "age":25})");

std::string name;
if (doc / "name" >> name) {
    // 成功提取
}

int age;
if (doc / "age" >> age) {
    // 成功提取
}

/* use method: get() */
{
    std::string name;
    if (doc["name"].get(name)) {
    }

    int age;
    if (doc["age"].get(age)) {
    }
}

2.21.4 数组尾部删除 pop

语法MutableValue >> MutableValue&

功能:在 Json 数组尾部删除元素

  • 左侧参数类型MutableValue(json 数组)
  • 右侧参数类型MutableValue&
  • 返回值类型MutableValue& 左参自身引用

从尾部删除,支持链式调用。方法要求有 MutableValue 左值接收被删除的结点,实际 业务可弃用。

yyjson::MutableDocument mutDoc(R"({"items":[10, 20, 30]})");
yyjson::MutableValue p1, p2, p3;
mutDoc / "items" >> p3;
mutDoc / "items" >> p2 >> p1;

/* use method: pop() */
{
    yyjson::MutableDocument mutDoc(R"({"items":[10, 20, 30]})");
    yyjson::MutableValue p1, p2, p3;
    mutDoc["items"].pop(p3);
    mutDoc["items"].pop(p2).pop(p1);
}

2.21.5 对象尾部删除 pop

语法MutableValue >> KeyValue&

功能:在 Json 对象尾部删除元素

  • 左侧参数类型MutableValue(json 对象)
  • 右侧参数类型KeyValue&
  • 返回值类型MutableValue& 左参自身引用
yyjson::MutableDocument mutDoc(R"({"user":{"name": "Alice", "age":25}})");
yyjson::KeyValue name, age;
mutDoc / "user" >> age >> name;

/* use method: pop() */
{
    yyjson::MutableDocument mutDoc(R"({"user":{"name": "Alice", "age":25}})");
    yyjson::KeyValue name, age;
    mutDoc["user"].pop(age).pop(name);
}

2.21.6 数组迭代器定点删除 remove

语法iteratorT >> MutableValue&

功能:可写数组迭代器定点删除当前元素,迭代器指向下一个元素

  • 左侧参数类型iteratorT 限可写数组迭代器
  • 右侧参数类型MutableValue&
  • 返回值类型iteratorT& 左参自身引用

对应的 remove 方法返回 MutableValue ,但 >> 返回自身引用支持链式删除。 如果链式调用 it.remove().remove() ,第二个 remove 其实是尝试对第一个删除 返回的临时值(将其视为数组)再作删除,往往不是想要的。

yyjson::MutableDocument mutDoc(R"({"items":[10, 20, 30]})");
yyjson::MutableValue p1, p2,p3;
auto it = mutDoc / "items" % 0;
it >> p1;
it >> p2 >> p3;

/* use method: remove() */
{
    yyjson::MutableDocument mutDoc(R"({"items":[10, 20, 30]})");
    auto it = mutDoc / "items" % 0;
    auto p1 = it.remove();
    auto p2 = it.remove();
    auto p3 = it.remove();
}

2.21.7 对象迭代器定点删除 remove

语法iteratorT >> KeyValue&

功能:可写对象迭代器定点删除当前键值对,迭代器指向下一个键值对

  • 左侧参数类型iteratorT 限可写对象迭代器
  • 右侧参数类型KeyValue&
  • 返回值类型iteratorT& 左参自身引用

对应的 remove 方法返回 KeyValue ,但 >> 返回自身引用支持链式删除。 KeyValue 没有 remove 方法,所以尝试对此链式调用 remove 将编译错误。

yyjson::MutableDocument mutDoc(R"({"user":{"name": "Alice", "age":25}})");
yyjson::KeyValue name, age;
auto it = mutDoc / "user" % "";
it >> name >> age;

/* use method: remove() */
{
    yyjson::MutableDocument mutDoc(R"({"user":{"name": "Alice", "age":25}})");
    auto it = mutDoc / "user" % "";
    auto name = it.remove();
    auto age = it.remove();
}

2.22 有序性比较 <

2.22.1 Json 结点比较 less

语法jsonT < jsonT

功能:Json 有序性定义小于比较

  • 左侧参数类型jsonT
  • 右侧参数类型jsonT
  • 返回值类型bool

按如下规则定义两个 json 的大小,在需要的场合下规定某种确定的顺序:

  • 首先按 yyjson 内部定义的类型编号(枚举)比较,如 null 最小,对象最大;
  • 相同类型的标量,按值比较,Number 类型统一转 double 比较大小,String 类型采用 strcmp 比较,Bool 值的 falsetrue 比,Null 值只有一个是相等的;
  • 容器类型,只比较容器大小,不递归比较,如果容器大小相同,按内存地址比较。
yyjson::Document doc = R"({"a": 10, "b": 20})"_xyjson;
bool result = doc / "a" < doc / "b";

/* use method: less() */
{
    auto result = doc["a"].less(doc["b"]);
}

2.22.2 其他有序性比较操作符

除了 < 小于比较,还有其他几种大小比较,但都可以从 < 推导,所以一般自定义类 只需重载 less 方法。

  • 大于 >a > bb < a 推导;
  • 小于等于 <=a <= b!(b < a) 推导;
  • 于于等于 >=a >= b!(a < b) 推导;

理论上,相等比较 == 也可由 !(a < b) && !(b < a) 推导。但是 xyjson 为各个核 心类都定义了自己的 == 比较操作。

2.23 相等性比较 ==

2.23.1 Json 结点值比较 equal

语法jsonT == jsonT

功能:深度比较两个 json 树的内容,即递归比较其类型、容器大小及内容。

  • 左侧参数类型jsonT
  • 右侧参数类型jsonT
  • 返回值类型bool
yyjson::Document doc1 = R"({"name": "Alice"})"_xyjson;
yyjson::Document doc2 = R"({"name": "Alice"})"_xyjson;
auto root1 = *doc1;
auto root2 = *doc2;

bool result = root1 == root2;

/* use method: equal() */
{
    bool result = root1.equal(root2);
}

2.23.2 Json 叶结点与基本标量类型比较

语法jsonT == value

功能:Json 结点值与给定值比较,要求类型匹配且内容相等。

  • 左侧参数类型jsonT
  • 右侧参数类型scalarT
  • 返回值类型bool
yyjson::Document doc = R"({"name":"Alice", "age": 25})"_xyjson;

bool nameMatch = doc / "name" == "Alice"; // true
bool ageMatch = doc / "age" == 25; // true
bool ageFloatMatch = doc / "age" == 25.0; // false

2.23.3 迭代器比较

语法iteratorT == iteratorT

功能:比较两个迭代器是否相等

  • 左侧参数类型iteratorT
  • 右侧参数类型iteratorT
  • 返回值类型bool

迭代器的很多操作前提都要判断迭代器是否失效,或者迭代器是否相等。只有从相同容器 创建的迭代器才可能相等,然后比较其当前索引。从相同容器任意起点创建的迭代器,经 过不断前进的前进,应该都能达到相同的终点失效状态。

yyjson::Document doc = R"({"user": {"name": "Alice", "age": 30}})"_xyjson;

// 用迭代器判断键是否存在
auto it = doc / "user" % "age";
if (it == doc["user"].endObject()) {} // false

/* use method: equal() */
if (it.equal(doc["user"].endObject())) {} // false

// 更简洁的判断
if(doc / "user" / "age") {} // true
if(doc / "user" % "age") {} // true

2.23.4 不等比较 !=

在 xyjson 库中,所有重载定义了 == 的类,也都支持 != 操作,后者明显可以且应 该由前者推导,也即相当于调用 !equal() 方法。

这在 begin/end 迭代器对循环中尤为常见。

yyjson::Document doc = R"({"items":[10, 20, 30]})"_xyjson;

int sum = 0;
for (auto it = doc["items"].beginArray(); it != doc["items"].endArray(); ++it)
{
    sum += *it | 0;
}

/* use method: equal() */
sum = 0;
for (auto it = doc["items"].beginArray(); !it.equal(doc["items"].endArray()); ++it)
{
    sum += *it | 0;
}

2.24 按位与运算 & - 类型判断

2.24.1 基本类型判断 isType

语法jsonT & value

功能:类型判断,使用字面量作为类型指示器

  • 左侧参数类型jsonT
  • 右侧参数类型scalarT 或类型常量
  • 返回值类型bool
yyjson::Document doc = R"({"name": "Alice", "age": 30})"_xyjson;
bool isString = doc / "name" & "";
bool isInt = doc / "age" & 0;
bool isReal = doc / "age" & 0.0;
bool isObject = doc.root() & "{}";
bool isArray = doc.root() & "[]";

/* use method: isType() */
{
    bool isString = doc["name"].isType("");
    bool isInt = doc["age"].isType(0);
    bool isReal = doc["age"].isType(0.0);
    bool isObject = doc.root().isType("{}");
    bool isArray = doc.root().isType("[]");
}

说明: 操作符 &isType 方法也接收类型常量作为参数,详情参考类型常量章节。

2.25 按位或运算 |

2.25.1 带默认值提取 getor

语法jsonT | defaultValue

功能:从 Json 结点提取值,如果无效或类型不匹配则返回默认值

  • 左侧参数类型jsonT
  • 右侧参数类型scalarT(基本类型)
  • 返回值类型:与右侧参数类型相同

带默认值提取操作符 | 是 xyjson 推荐的常用操作。基本上能用于 & 类型判断的参 数,也能用于 | 参数作为默认值提取。如果 & 类型判断返回 true ,那么 | 操作能转换提取 json 值,否则返回右侧的默认参数值。大部分情况下,没必要显式用 & 额外做类型判断,只要用 | 默认提取即可。

yyjson::Document doc = R"({"name": "Alice", "age": 30, "active": true})"_xyjson;
const char* pszName = doc / "name" | "";
std::string name = doc / "name" | "";
int age = doc / "age" | 0;
bool active = doc / "active" | false;

/* use method: getor() */
{
    const char* pszName = doc["name"].getor("");
    std::string name = doc["name"].getor("");
    int age = doc["age"].getor(0);
    bool active = doc["active"].getor(false);
}

注意在上例中 |"" 返回 C-Style 的字符串 const char* ,但可以赋值给 std::string ,相当于调用后者的构造函数。也支持 | std::string() 直接返回 std::string ,虽不如 |"" 简洁,但意义明了。另外也支持 | kString 等类型常 量。

2.25.2 管道函数转换 pipe

语法jsonT | function

功能:对 Json 值应用管道函数进行转换

  • 左侧参数类型jsonT
  • 右侧参数类型std::function 等任意可调用对象
  • 返回值类型:与右侧函数返回值类型相同

右侧函数支持接收一个 jsonT 类型(ValueMutableValue)的参数,也支持基本 类型(数字或字符串),如果是后者,pipe 方法相当于先调用 getor 方法将左侧 json 转为基本类型(类型不匹配时返回目标类型的零值)再调用右侧函数,否则直接将 左侧 json 传给右侧函数。因此,pipe 相当于 getor 的超集,getor 相当于 pipe 的最常用基础版。

yyjson::Document doc = R"({"name": "alice"})"_xyjson;
std::string upper = doc / "name" | [](const std::string& s) {
    std::string r = s;
    std::transform(r.begin(), r.end(), r.begin(), ::toupper);
    return r;
};

/* use method: pipe() */
{
    std::string upper = doc["name"].pipe([](const std::string& s) {
        std::string r = s;
        std::transform(r.begin(), r.end(), r.begin(), ::toupper);
        return r;
    });
}

2.26 赋值操作符 =

2.26.1 自定义类的拷贝赋值

封装的 jsonT 类,相当于是 yyjson 底层结点的代理指针,它是值类,允许拷贝赋值。 多个拷贝的 Value 值引用相同的底层结点。但不建议拷贝 MutableValue 值,因为 通过一个拷贝修改底层结点也会影响另一个拷贝的观察或操作,容易混乱误解。

迭代器 iteratorT 类,也是值类,可以拷贝赋值,但是迭代器内部成员比 jsonT 复杂, 应该尽量避免拷贝赋值。

但是封装的 docT 类,它代理了 yyjson 底层的文档树与内存池,不允许拷贝赋值,只允 许移动赋值。但一般业务中也不建议移动 docT 文档对象。

2.26.2 可写结点赋值 set

语法MutableValue = value

功能:为可写的 json 标量叶结点赋新值,可以改变 json 标量类型,但避免对非空 容器重新赋值。

  • 左侧参数类型MutableValue
  • 右侧参数类型scalarTjsonT、或类型常量
  • 返回值类型MutableValue&
yyjson::MutableDocument mutDoc(R"({"name": "Alice"})");
mutDoc / "name" = "Bob";
mutDoc["age"] = 30;
mutDoc / "active" = true; // 无效果
//^ 结果:{"name":"Bob","age":30}

/* use method: set() */
{
    yyjson::MutableDocument mutDoc(R"({"name": "Alice"})");
    (mutDoc / "name").set("Bob");
    mutDoc["age"].set(30);
    (mutDoc / "active").set(true);
}

注意,不能通过 / 为不存在的键赋值,那没有效果。[] 可以自动添加不存在的键, 先默认创建并添加 null 类型的 json 结点,再赋值。

2.27 复合赋值操作符

重载了一些复合赋值操作符,那是其他操作符与 = 的自然衍生组合。

2.27.1 Json 复合赋值提取 |=

语法scalarT |= jsonT

功能:从 Json 结点提取值并赋给原变量

  • 左侧参数类型scalarT&
  • 右侧参数类型jsonT
  • 返回值类型scalarT&

从内部实现上讲,|= 操作比 | 更简捷,那直接调用 get 方法,而 | 调用 getor 。当类型不匹配时,不影响左参原值。

yyjson::Document doc = R"({"age": 30})"_xyjson;
int age = 18;
age |= doc / "age";  // age = 30

/* use method: get() */
{
    int age = 18;
    doc["age"].get(age);
}

2.27.2 迭代器多步前进 +=

语法iteratorT += n

功能:迭代器多步前进,不会拷贝新迭代器

  • 左侧参数类型iteratorT&
  • 右侧参数类型size_t
  • 返回值类型iteratorT&
yyjson::Document doc = R"({"items": [10, 20, 30, 40]})"_xyjson;
auto iter = doc / "items" % 0;
iter += 3;  // 跳过 3 个元素

/* use method: advance() */
{
    auto iter = doc / "items" % 0;
    iter.advance(3);
}

2.27.3 迭代器重定位 %=

语法iteratorT %= target

功能:迭代器重定位,不会拷贝新迭代器

  • 左侧参数类型iteratorT&
  • 右侧参数类型size_tconst char*
  • 返回值类型iteratorT&
yyjson::Document doc = R"({"items": [10, 20, 30, 40, 50]})"_xyjson;
auto iter = doc / "items" % 4;
iter %= 2;  // 重定位到索引 2

/* use method: advance() */
{
    auto iter = doc / "items" % 4;
    iter.advance(2);
}

2.28 Document 自动转调 Root 的操作符

以下操作符在 Document 上使用时,会自动转调 root() 方法:

操作符 实际调用 示例
doc[key] root().index(key) doc["user"]["name"]
+doc +root() +doc(转整数)
-doc -root() -doc(转字符串)
doc / path root().pathto(path) doc / "user/name"
doc % key root().iterator(key) doc % ""
os << doc os << root().toString() std::cout << doc
doc == doc root() == other.root() doc1 == doc2

这些操作符在 Document 上的行为与在 Value 上基本一致,主要用于简化代码。

2.28.1 索引操作符 []

语法docT[key]docT[index]

功能:单层索引根结点,作用于可写文档时会自动创建新字段

  • 左侧参数类型docT
  • 右侧参数类型stringT(对象键名)或 size_t(数组索引)
  • 返回值类型jsonT(与 docT 对应的 Value 类型)
yyjson::Document array("[10, 20, 30]");
yyjson::MutableDocument object("{}");

auto first = array[0];
object["first"] = 10;

/* use method: root() */
{
    auto first = array.root()[0];
    object.root()["second"] = 20;
}

2.28.2 一元正号 + 转整数

语法+docT

功能:或取根结点转整数

  • 参数类型docT
  • 返回值类型int
yyjson::Document doc = R"([10, 20, 30])"_xyjson;
int size = +doc;  // 3

/* use method: root() */
{
    int size = +doc.root(); // 3
}

2.28.3 一元负号 - 转字符串

语法-docT

功能:取根结点转字符串

  • 参数类型docT
  • 返回值类型std::string
yyjson::Document doc = R"({"name": "Alice"})"_xyjson;
std::string str = -doc; // {"name":"Alice"}

/* use method: root() */
{
    std::string str = -doc.root();
}

2.28.4 除法 / 路径查找

语法docT / keydocT / pathdocT / index

功能:类似 [] 索引的单层访问与线性查找,更方便链式逐级访问

  • 左侧参数类型docT,常规 Json 根结点是对象或数组
  • 右侧参数类型stringT(对象键名)或 size_t(数组索引)
  • 返回值类型jsonT(与文档类型对应的结点类型)
yyjson::Document doc = R"({"user": {"name": "Alice", "age": 30},
    "items": [10, 20, 30]})"_xyjson;

auto name = doc / "user" / "name";
auto age = doc / "/user/age";
auto item = doc / "/items/0";

/* use method: root() */
{
    auto name = doc.root() / "user" / "name";
    auto age = doc.root() / "/user/age";
    auto item = doc.root() / "/items/0";
}

2.28.5 取模 % 创建迭代器

语法docT % keydocT % index

功能:为根结点创建迭代器,可指针起始索引或起始键名

  • 左侧参数类型docT,要求对象或数组类型
  • 右侧参数类型const char*(对象键名)或 size_t(数组索引)
  • 返回值类型iteratorT(根结点对应的迭代器类型)
yyjson::Document array("[10, 20, 30]");
yyjson::Document object(R"({"name": "Alice", "age": 30})");

auto arrIt = array % 0;
auto objIt = object % "";

/* use method: root() */
{
    auto arrIt = array.root() % 0;
    auto objIt = object.root() % "";
}

2.28.6 左移 << 标准流输出

语法std::ostream << docT

功能:整个文档序列化输出

  • 左侧参数类型std::ostream&
  • 右侧参数类型docT
  • 返回值类型std::ostream&
yyjson::Document doc = R"({"name": "Alice"})"_xyjson;
std::cout << doc << std::endl;
std::ostringstream oss1;
oss1 << doc;

/* use method: root() */
std::cout << doc.root() << std::endl;
std::ostringstream oss2;
oss2 << doc.root();

// 格式化输出需显式调用 toString(true)
std::cout << doc.root().toString(true) << std::endl;
std::ostringstream oss3;
oss3 << doc.root().toString(true);

2.28.7 相等操作符 ==

语法docT == docT

功能:两个文档内容是否相等

  • 左侧参数类型docT
  • 右侧参数类型docT
  • 返回值类型bool
yyjson::Document doc1 = R"({"name": "Alice"})"_xyjson;
yyjson::Document doc2 = R"({"name": "Alice"})"_xyjson;
bool docEqual = doc1 == doc2;

/* use method: root() */
{
    bool rootEqual = doc1.root() == doc2.root();
}

注意:比较两个 doc 相等的条件是它们的根结点内容相同。持有两个相同 yyjson 底层 的 doc 指针的 Document 类对象不一样相等,因为它们的根结点可能不一样。Document 的内存池可以创建多个 json 结点,其中任何一个结点都可以被设为根结点,不一定所有 结点都可以从根结点有路径可达。当然这种情况比较罕见,一般业务中不建议重设根结点 。那么 doc 指针相同的也往往意味着根结点指针相同,那么其内容也一定可以快速判断 为相等。

3 类型常量应用

xyjson 定义了一系列类型代表值常量,可以用于一些操作符的右侧参数。因为操作数只 能是值,不能是类型,所以定义一些具名的有意义常量,比直接用字面量增加可读性。当 然也需要交换简洁性,可考虑使用 using 引入命名空间。

namespace yyjson {
    // 基础类型常量
    constexpr std::nullptr_t kNull = nullptr;
    constexpr bool kBool = false;
    constexpr int kInt = 0;
    constexpr int64_t kSint = 0L;
    constexpr uint64_t kUint = 0uL;
    constexpr double kReal = 0.0;

    // 特殊标记类型
    struct ZeroNumber {};
    struct EmptyString {};
    struct EmptyArray {};
    struct EmptyObject {};

    // 类型常量实例
    constexpr ZeroNumber kNumber;
    constexpr EmptyString kString;
    constexpr EmptyArray kArray;
    constexpr EmptyObject kObject;

    // 底层指针类型
    constexpr yyjson_val* kNode = nullptr;
    constexpr yyjson_mut_val* kMutNode = nullptr;
    constexpr yyjson_mut_doc* kMutDoc = nullptr;
}

注意几点:

  • 字面量 0 相当于 kInt ,如果想表达大整数,推荐使用 kSintkUint , 这也与 yyjson 底层结点内存储整数的命名更一致。
  • 字面量 0.0 只对应对实数或 double 类型的 kReal ,而 kNumber 是对应 Json 类型的 Number 概念,它是实数与整数的统称;在 C/C++ 中尽量用更强的类型。
  • 字面量 "" 相当于 kString 。但有两个特殊字面 "[]""{}" 在一些操作 中分别表示数组与对象类型。为了更强类型的区分,额外定义了三个空类的常量。

3.1 类型判断

yyjson::Document doc = R"({"name": "Alice", "age": 30})"_xyjson;

// 使用类型常量
{
    using namespace yyjson;
    bool isString = doc / "name" & kString; // true
    bool isInt = doc / "age" & kInt;        // true
    bool isNumber = doc / "age" & kNumber;  // true
    bool isDouble = doc / "age" & kReal;    // false
}

// 使用字面量
{
    bool isString = doc / "name" & "";   // true
    bool isInt = doc / "age" & 0;        // true
    // isNumber 只能用 kNumber 判断
    bool isDouble = doc / "age" & 0.0;   // false
}

3.2 值提取

在大部分情况下,getor 的操作符 | 右侧都是某个类型的零值,这适合用类型常量 。如果要提供其他默认值,可用字面量或变量。

yyjson::Document doc = R"({"name": "Alice", "age": 30})"_xyjson;

// 使用类型常量
{
    using namespace yyjson;
    const char* pszName = doc / "name" | kString; // Alice
    int iAge = doc / "age" | kInt;      // 30
    double fAage = doc / "age" | kReal; // 0.0
    double nAage = doc / "age" | kNumber; // 30.0
}

// 使用字面量
{
    const char* pszName = doc / "name" | ""; // Alice
    int iAge = doc / "age" | 0;      // 30
    double fAage = doc / "age" | 0.0; // 0.0
    // kNumber 没有对应的字面量,或者可以用 ZeroNumber 构造函数
    double nAage = doc / "age" | ZeroNumber(); // 30.0
}

3.3 赋值修改

类型常量代表值都是各个类型的零值或空值,所以用于 = 右侧时将 json 内容改为对 应类型的零值或空值。

yyjson::MutableDocument mutDoc(R"({"name": "Alice", "age": 30})");

// 使用类型常量
{
    using namespace yyjson;
    mutDoc / "name" = kString;
    mutDoc / "age" = kInt;
}

// 使用字面量
{
    mutDoc / "name" = "";
    mutDoc / "age" = 0;
}

3.4 特殊容器类型 kArray kObject

为 json 容器类型定义了两个常量,kArray 表示数组,kObject 表示对象。它们相 对其他标量类型常量更复杂些。ValueMutableValue 的数组与对象也有些不同要 注意。

除了 | 操作特殊外,在其他一些操作符或方法中,也可以用 "[]" 代替 kArray, 用 "{}" 代替 kObject。但这两个特殊字面量是运行期作特殊判断的,kArraykObject 是编译期决定类型的。所以在性能敏感的地方最好使用类型常量,能略微提升 性能。

3.4.1 容器类型判断

yyjson::Document doc = R"({"user": {"name": "Alice", "age": 30},
    "items": [10, 20, 30]})"_xyjson;

// 使用类型常量
{
    using namespace yyjson;
    bool isArray = doc / "items" & kArray;    // true
    bool isObject = doc / "user" & kObject;   // true
}

// 使用字面量
{
    bool isArray = doc / "items" & "[]"; // true
    bool isObject = doc / "user" & "{}";    // true
}

3.4.2 容器类型转换

在值提取操作符 | 右侧,不能用 "[]" 表示数组。因为 "[]""{}" 这两个 特殊字符串字面量的类型其实还是字符串,调用 getor(const char\*) 的重载,故与 | "" 一样表示提取字符串类型的 json 值。json | "[]" 操作的意义是,当 json 不是字符串类型时,返回默认值长度为 2 的字符串 "[]"

但是在 | 右侧可以使用 kArraykObject ,当类型匹配时,它返回 ValueMutableValue 的一个子类专门用于表示数组或对象。

  • Value | kArray 返回 ConstArray
  • Value | kObject 返回 ConstObject
  • MutableValue | kArray 返回 MutableArray
  • MutableValue | kObject 返回 MutableObject

这四个子类分别继承了父类的所有方法,可以当作 ValueMutableValue 使用。 只额外增加两个方法 beginend 分别表示创建起始与结束迭代器,使之能适配标 准容器迭代器的接口,能够使用范围 for 与一些标准库算法。

也就是说,Value 类只能用 beginArrayendArray 方法创建数组迭代器,这是 非标准接口。而 ConstArray 可以使用 beginend 标准接口创建数组迭代器, 因为这个类只表示数组。其他子类的意义类似。

在这个操作下,| kArray 对应 array() 方法,| kObject 对应 object() 方法 。

yyjson::Document doc = R"({"user": {"name": "Alice", "age": 30},
    "items": [10, 20, 30]})"_xyjson;

auto array = doc / "items" | yyjson::kArray;
auto object = doc / "user" | yyjson::kObject;

// 使用具名方法
{
    auto array = doc["items"].array();
    auto object = doc["user"].object();
}

// 支持标准容器操作
int arraySum = 0;
for (auto value : array) {
    arraySum += value | 0;
}

int objectCount = 0;
for (auto value : object) {
    objectCount++;
}

// 等效显式使用 begin/end 迭代器对
{
    int arraySum = 0;
    for (auto it = array.begin(); it != array.end(); ++it) {
        auto value = *it;
        arraySum += value | 0;
    }
}

{
    int objectCount = 0;
    for (auto it = object.begin(); it != object.end(); ++it) {
        auto value = *it;
        objectCount++;
    }
}

3.4.3 变更容器类型

kArraykObject 用于 = 右侧时,表示将左侧的可写 json 重设为空数组或 空对象。

yyjson::MutableDocument mutDoc(R"({"name": "Alice", "age": 30})");
mutDoc / "name" = yyjson::kObject;
mutDoc / "age" = yyjson::kArray;
//^ 结果:{"name":{},"age":[]}

// 使用字面量
{
    yyjson::MutableDocument mutDoc(R"({"name": "Alice", "age": 30})");
    mutDoc / "name" = "{}";
    mutDoc / "age" = "[]";
}

// 使用具名方法
{
    yyjson::MutableDocument mutDoc(R"({"name": "Alice", "age": 30})");
    mutDoc["name"].setObject();
    mutDoc["age"].setArray();
}

3.4.4 创建空容器

kArraykObject 用于 = 右侧表示修改当前左侧参数既有 json 类型不同, 当它们用于 << 操作符右侧时,将自动调用 MutableDocument::create() 方法先创 建空数组或空对象结点,再插入到当前操作的左侧参数容器中。

yyjson::MutableDocument mutDoc("{}");
*mutDoc << "user" << yyjson::kObject;
*mutDoc << "items" << yyjson::kArray;

// 使用字面量
{
    yyjson::MutableDocument mutDoc("{}");
    *mutDoc << "user" << "{}";
    *mutDoc << "items" << "[]";
}

mutDoc / "user" << "name" << "Alice" << "age" << 25;
mutDoc / "items" << 10 << 20 << 30;

注意:kArraykObject 类型常量是设计为给 Value 类的操作符使用的。 Document 类的构造函数中传 "{}" 就表示解析 json 字符串,恰好它也是表示空对象 。构造函数不支持 kObjectkArray 参数,但是可写文档在构造之后可以重设 根结点的类型。

yyjson::MutableDocument mutDoc; // 默认构造是空对象 "{}"
mutDoc.root() = yyjson::kArray;

mutDoc.root() << 10 << 20 << 30;
//^ 结果:[10,20,30]

4 获取 yyjson 底层指针

xyjson 提供了一些方法可以从封装类中获取底层 yyjson 结构体指针,可用于想直接调 用 C API 的场景。

4.1 使用无参数的 get 方法

  • Value::get() 返回 yyjson_val*
  • Document::get() 返回 yyjson_doc*
  • MutableValue::get() 返回 yyjson_mut_val*
  • MutableValue::getDoc() 返回 yyjson_mut_doc*
  • MutableDocument::get() 返回 yyjson_mut_doc*
yyjson::Document doc(R"({"name": "Alice", "age":25})");
yyjson::MutableDocument mutDoc = ~doc;

yyjson_doc* ptrDoc = nullptr;
yyjson_val* ptrVal = nullptr;
ptrDoc = doc.get();             // not null
ptrVal = doc["name"].get();     // not null
ptrVal = (doc / "nokey").get(); // nullptr
ptrVal = (doc["nokey"]).get();  // nullptr

yyjson_mut_doc* ptrMutDoc = nullptr;
yyjson_mut_val* ptrMutVal = nullptr;
ptrMutDoc = mutDoc.get();          // not null
ptrMutVal = mutDoc["name"].get();     // not null
ptrMutDoc = mutDoc["name"].getDoc();  // not null
ptrMutVal = (mutDoc / "nokey").get(); // nullptr
ptrMutVal = (mutDoc["nokey"]).get();  // not null, auto insert
ptrMutVal = (mutDoc / "nokey").get(); // not null

4.2 使用 >> 操作符或 get 方法

右侧参数是结构体指针的引用,将修改参数的指针值,返回 bool 表示获取成功。

  • Value >> yyjson_val*&Value::get(yyjson_val*&)
  • MutableValue >> yyjson_mut_val*&MutableValue::get(yyjson_mut_val*&)
  • MutableValue >> yyjson_mut_doc*&MutableValue::get(yyjson_mut_doc*&)

左侧参数只支持 Value 类,不支持 Document 类,因为 >> 操作符的主要目的是提取 json 叶结点至标量。MutableValue 类中也有 yyjson_mut_doc* 成员,可以提取。 但不能从 Value 类中提取 yyjson_doc*

yyjson::Document doc(R"({"name": "Alice", "age":25})");
yyjson::MutableDocument mutDoc = ~doc;

yyjson_val* ptrVal = nullptr;
yyjson_mut_doc* ptrMutDoc = nullptr;
yyjson_mut_val* ptrMutVal = nullptr;

if (doc / "name" >> ptrVal) { }      // true
if (mutDoc / "name" >> ptrMutVal) {} // true
if (mutDoc / "name" >> ptrMutDoc) {} // true

if (doc / "nokey" >> ptrVal) { }      // false
if (mutDoc / "nokey" >> ptrMutVal) {} // false
if (mutDoc["nokey"] >> ptrMutVal) {}  // true
if (mutDoc / "nokey" >> ptrMutVal) {} // true

4.3 使用 c_ 系列函数

命名源于 std::stringc_str 方法获取 C-Style 的字符串 const char*, xyjson 的各个封装类也定义了相应的 c_ 方法获取底层 C 结构体指针。

  • Value::c_val() 返回 yyjson_val*
  • Document::c_doc() 返回 yyjson_doc*
  • MutableValue::c_val() 返回 yyjson_mut_val*
  • MutableValue::c_doc() 返回 yyjson_mut_doc*
  • MutableDocument::c_doc() 返回 yyjson_mut_doc*
  • iteratorT::c_iter() 返回各迭代器内部的 C 迭代器结构体指针
  • iteratorT::c_val() 返回迭代器当前值结点 yyjson_val*yyjson_mut_val*
  • iteratorT::c_key() 返回迭代器当前键结点 yyjson_val*yyjson_mut_val*

其中,数组迭代器没有定义 c_key 方法。失效的迭代器 c_valc_key 方法返 回空指针,但 c_iter 返回值始终非空。

yyjson::Document doc(R"({"user":{"name": "Alice", "age":25},
    "items":[10, 20, 30]})");
yyjson::MutableDocument mutDoc = ~doc;

yyjson_val* ptrVal = nullptr;
yyjson_doc* ptrDoc = nullptr;
yyjson_mut_doc* ptrMutDoc = nullptr;
yyjson_mut_val* ptrMutVal = nullptr;

// 以下 c_ 方法都返回非空指针
ptrDoc = doc.c_doc();
ptrVal = doc["user"].c_val();
ptrMutDoc = mutDoc.c_doc();
ptrMutDoc = mutDoc["user"].c_doc();
ptrMutVal = mutDoc["user"].c_val();

auto ita = doc / "items" % 0;
auto ito = doc / "user" % "";
auto mita = mutDoc / "items" % 0;
auto mito = mutDoc / "user" % "";

// 以下 c_ 方法也返回非空指针
bool itaIter = (ita.c_iter() != nullptr);
bool itoIter = (ito.c_iter() != nullptr);
bool mitaIter = (mita.c_iter() != nullptr);
bool mitoIter = (mito.c_iter() != nullptr);

bool itaVal = (ita.c_val() != nullptr);
bool itoVal = (ito.c_val() != nullptr);
bool mitaVal = (mita.c_val() != nullptr);
bool mitoVal = (mito.c_val() != nullptr);

bool itoKey = (ito.c_key() != nullptr);
bool mitoKey = (mito.c_key() != nullptr);

//! 数组迭代器没有定义 c_key 方法
//! if (ita.c_key() != nullptr) {}
//! if (mita.c_key() != nullptr) {}

5 条件编译宏功能控制

xyjson 提供了一些条件编译宏用于在具体项目禁用不必要的功能,默认是所有功能开启 的。这些宏可以在构建系统(如 CMakeLists.txt)中定义,也可以在 #include "xyjson.h" 之前手动 #define 。建议使用前者,能保证在整个项目中使 用一样的条件编译宏,避免在不同编译单元中包含同一个头文件却实际编译不同的定义而 出现混乱。

注意,只需定义这些宏,而不必定义其值,其值定义为 10 都认为已定义,效果 都一样,表达禁用的含义。

5.1 禁用可写模型 XYJSON_DISABLE_MUTABLE

若定义了该宏,所有可写模型类,包括 MutableDocumet 、MutableValue 及其相应的迭 代器类与容器子类,都不可用。

yyjson 库的只读模型效率比可写模型高很多,如果项目没有可写需求,只需用到读 Json 的功能,可定义该宏。xyjson 的头文件中也有远超一半的代码在处理可写的问题,如果 禁用可写,将大幅降低实际代码量,编译速度也将有所提升。

5.2 禁用对象链式插入 XYJSON_DISABLE_CHAINED_INPUT

若定义该宏,则不再支持可写对象及其迭代器链式插入,如 object << key << valueobjectIterator << key << value 将不能通过编译。

禁用该功能,可使 MutableValue 类的大小从 24 字节降为 16 字节(64位操作系统) 即两个指针的大小。由于其他许多操作,如路径操作、迭代器解引用操作等都返回 MutableValue 的值,小对象不仅减少内存占用,也能提升运行效率。