Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# For integration in a ESP project
# For integration in a ESP project
# See https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html#using-third-party-cmake-projects-with-components
if (ESP_PLATFORM)

Expand Down Expand Up @@ -33,6 +33,10 @@ else()

enable_testing()
add_subdirectory (test)
option(SIMPLESON_BUILD_BASELINE_BENCHMARKS "Build baseline benchmark targets" ON)
if(SIMPLESON_BUILD_BASELINE_BENCHMARKS)
add_subdirectory (baseline_benchmark_test)
endif()
add_subdirectory (examples)
add_subdirectory (issues)

Expand Down
253 changes: 173 additions & 80 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,102 +1,195 @@
| Linux / Windows / macOS | ESP |
| :---------------------: | :-: |
| [![Build status](https://ci.appveyor.com/api/projects/status/h9avws048watkvnr/branch/master?svg=true)](https://ci.appveyor.com/project/gregjesl/simpleson/branch/master) | [![Build status](https://ci.appveyor.com/api/projects/status/b8deqd4o1ilb2o3b/branch/master?svg=true)](https://ci.appveyor.com/project/gregjesl/simpleson-esp/branch/master) |

# simpleson
Lightweight C++ JSON parser & serializer that is C++98 compatible with no dependencies

[Github Repository](https://github.com/gregjesl/simpleson)
轻量级 C++ JSON 解析与序列化库,基于 [gregjesl/simpleson](https://github.com/gregjesl/simpleson) 的 **C++17 现代化与性能改造**版本,除 Google Test / Google Benchmark 等**测试依赖**外,库本身无第三方运行时依赖。

## 文档导航

| 文档 | 内容 |
| ---- | ---- |
| `现代C++改造记录.md` | 按迭代记录语法、安全与工程化改动 |
| `任务二.md` | 性能优化压缩报告: |
| `baseline_benchmark_test/BASELINE_RESULTS.md` | 完整内存与 Google Benchmark 原始数据、迭代对照 |

## 项目概述

在**保持 JSON 语义与对外用法基本一致**的前提下,本仓库对 simpleson 做了:

- **正确性与 UB 修复**(如 `std::isspace`、异常规范、`tryparse` 捕获范围等)
- **性能**:序列化预分配、数字转字符串、对象模式 **FNV-1a + `unordered_multimap` 键索引** 等
- **工程质量**:单测、`operator==` 槽位比较、宏改内联、移动语义等

## 改造亮点

- **C++17**:`noexcept`、`override`、`using` 别名、`static_cast`、Rule of Five 等
- **安全**:消除部分未定义行为,收紧异常处理
- **性能**:大对象解析、序列化、Pretty、`Access_Key` 等见下表与 `任务二.md`
- **测试**:Google Test 语义测试;OpenCppCoverage 下 `json.cpp` / `json.h` 行覆盖率约 **91.6%**(以 `jobject_semantics_test/coverage_final` 报告为准)

## 性能结果(详细结果见任务二.md)

| 优化项 | 优化前(迭代 0) | 优化后(迭代 13) | 提升倍数 |
| ------ | ---------------- | ----------------- | -------- |
| 解析大对象 `BM_Parse_Object/500` | 524,576 ns | 180,664 ns | **2.90x** |
| 序列化 `BM_Serialize/100` | 8,161 ns | 2,400 ns | **3.40x** |
| Key 查找 `BM_Access_Key/100` | 854 ns | 79.5 ns | **10.74x** |
| Pretty `BM_Pretty/100` | 3,223 ns | 2,459 ns | **1.31x** |
| 解析大数组 `BM_Parse_Array/1000` | 149,613 ns | 139,509 ns | **1.07x** |
| 结构相等 `BM_StructuralEquals` ※ | 4,349 ns | 246 ns | **17.7x** |
| 混合对象序列化 `BM_Serialize_Mixed` ※ | 414 ns | 157 ns | **2.64x** |
| 布尔 / null / 字符串读 `BM_ReadBoolNullString` ※ | 230 ns | 199 ns | **1.16x** |
| 枚举键 + `has_key` `BM_ListKeys_HasKey/100` ※ | 2,131 ns | 1,904 ns | **1.12x** |

## 现代 C++ 改造

[Documentation](https://www.oldgreg.net/simpleson/1.1.0)
| 类别 | 示例 |
| ---- | ---- |
| 安全 / 规范 | `isspace` 用 `unsigned char`、`noexcept`、`catch (const std::exception&)` |
| 风格 | `typedef` → `using`,宏 → `inline` 函数,去掉 `goto`(如 `push_array`) |
| 资源 | `sub_reader` 智能指针、`nullptr` |
| API | `parse(const std::string&)` 为 `const` 引用等 |

## Why simpleson?
Simpleson is built under the following requirements:
- One header and one source file only
- No external dependencies
- ISO/IEC 14882:1998 (aka C++98) compatible
- Cross-platform
完整条目见 **`现代C++改造记录.md`**。

A primary use case for simpleson is in an memory-constrained embedded system.
## 单元测试

## Building simpleson
Simpleson was intentionally built such that a developer could simply copy [json.h](json.h) into the target project's `inc` folder, copy [json.cpp](json.cpp) into the `src` folder, and then compile the target project. No linking -> no drama.
- **框架**:Google Test(`find_package(GTest CONFIG REQUIRED)`)
- **目标**:`jobject_semantics_test`(`test/jobject_semantics_test.cpp`,大量用例覆盖 `json.h` / `json.cpp`)
- **运行**:构建后执行可执行文件,或使用 `ctest`(已 `gtest_discover_tests`)

Building the library and tests follows the standard build chain via CMake:
```bash
cmake -B build -S .
cmake --build build --config Release --target jobject_semantics_test
ctest --test-dir build -C Release -R jobject_semantics_test
```
mkdir build
cd build
cmake ../
make

Windows 下测试程序目录需能加载 **`simpleson.dll`**(CMake 已在 POST_BUILD 复制)。

### 覆盖率(OpenCppCoverage)

需 **带 PDB 的构建**(一般用 **Debug**,或为 Release 开启程序数据库)。示例(按本机路径改写 `--modules` 与 exe):

```powershell
opencppcoverage --modules "D:\path\to\build\Release" `
--sources "D:\path\to\homework\simpleson" `
--export_type html:"jobject_semantics_test\coverage_out" `
-- "D:\path\to\build\test\Release\jobject_semantics_test.exe"
```
Unit tests can then be run by executing `make test`

## Quickstart
历史报告可参考目录:`jobject_semantics_test/coverage_final/`。

```cpp
// Create the input
std::string input = "{ \"hello\": \"world\" }";

// Parse the input
json::jobject result = json::jobject::parse(input);

// Get a value
std::string value = (std::string)result.get_entry("hello").value

// Add entries
json::jobject example;
example["int"] = 123;
example["float"] = 12.3f;
example["string"] = "test string";
int test_array[3] = { 1, 2, 3 };
example["array"] = std::vector<int>(test_array, test_array + 3);
std::string test_string_array[2] = { "hello", "world" };
example["strarray"] = std::vector<std::string>(test_string_array, test_string_array + 2);
example["emptyarray"] = std::vector<std::string>();
example["boolean"].set_boolean(true);
example["null"].set_null();

// Reference values
std::string hello = example["strarray"][0];
std::string world = example["strarray"][1];

// Clear a value
example["hello"].clear();
example.remove("emptyarray");

// Serialize the new object
std::string serial = (std::string)result;
## 基准测试

| 目标 | 作用 | 主要依赖 |
| ---- | ---- | -------- |
| `baseline_benchmark_test_time` | 解析 / 序列化 / Pretty / `Access_Key` 等经典 `BM_*` | `benchmark::benchmark` |
| `baseline_benchmark_test_time_comprehensive` | 混合标量、`tryparse`、合并、`operator==` 等扩展场景 | 同上 |
| `baseline_benchmark_test_memory` | exp1~exp10 等堆分配与 checksum | **`allocounter`** 目标(见下) |

```bash
cmake --build build --config Release --target baseline_benchmark_test_time
cmake --build build --config Release --target baseline_benchmark_test_time_comprehensive
./build/baseline_benchmark_test/baseline_benchmark_test_time
```

## Using simpleson
**`allocounter`**:内存基准链接名为 `allocounter` 的 CMake 接口库。若单独配置本目录导致找不到该目标,需在**父工程**中 `add_subdirectory` 到提供 `allocounter.hpp` 的工程(例如课程仓库中的 `week02/practice/allocounter`),或自行提供同名 `IMPORTED` / `INTERFACE` 目标。

详细数字与迭代对比见 **`baseline_benchmark_test/BASELINE_RESULTS.md`**。

## 构建要求

- **C++17**
- **CMake 3.8+**
- **Google Test**:配置并运行 `jobject_semantics_test` 时需要(`GTest_DIR` / `CMAKE_PREFIX_PATH` 等)
- **Google Benchmark**:配置并运行时间/扩展基准时需要
- **Windows**:生成 **`simpleson` 共享库**(`simpleson.dll`),测试与基准通过 POST_BUILD 复制到可执行文件目录

The namespace of simpleson is simply `json`. JSON objects can be parsed by calling `json::jobject::parse()`, which takes a string and returns a `jobject`. The array operators are overloaded for `jobject`, meaning you can assign and access entries using the `[]` operators like many other software languages. Other useful methods of `jobject` include:
- `has_key("key")` - Returns true if the key exists in the `jobject`
- `remove("key")` - Removes they entry associated with the key if the key exists in the `jobject`
- `clear()` - Removes all entries in the `jobject`
- `["key"].set_boolean(true)` - [Sets the key to the boolean value](#a-note-on-booleans)
- `["key"].set_null()` - Sets the value associated with the key to null
- `["key"].is_true()` - Returns true if the boolean value associated with the key is true
- `["key"].is_null()` - Returns true if the value associated with the key is null
- `pretty()` - Serializes the object into a "pretty" string (using tabs and newlines)
示例(vcpkg 等已安装 GTest/Benchmark 时):

An instance of `jobject` can be searlized by casting it to a `std::string`. Note that an instance of `jobject` does not retain it's original formatting (it drops tabs, spaces outside strings, and newlines).
```powershell
cmake -B build -S . -DCMAKE_PREFIX_PATH="C:/vcpkg/installed/x64-windows"
cmake --build build --config Release
```

**嵌入式**:保留上游 ESP-IDF 分支逻辑,使用 `ESP_PLATFORM` 时走独立工程路径(见根目录 `CMakeLists.txt`)。

## 快速开始

### Arrays
Simpleson supports arrays as the root object:
```cpp
json::jobject example = json::jobject::parse("[1,2,3]");
int one = example[0]; // Value is 1
int two = example[1]; // Value is 2
int three = example[2]; // Value is 3
#include "json.h"
#include <iostream>

int main() {
std::string input = R"({"name": "Alice", "age": 30, "active": true})";
json::jobject person = json::jobject::parse(input);

std::string name = person["name"];
int age = person["age"];

if (person["active"].is_true()) {
std::cout << name << " is active\n";
}

json::jobject address;
address["city"] = "New York";
address["zip"] = 10001;
person["address"] = address;

std::cout << person.as_string() << "\n";
return 0;
}
```
Arrays are stored in a `jobject`. You can determine whether a `jobject` is holding an array through the method `is_array()`.

### Multi-level Access
To access elements several levels down, the `get(key)` and `array(index)` can be used for objects and arrays, respectively:
### 数组与嵌套

`proxy` 上**没有**连续的 `operator[]`;嵌套对象需先转为 `jobject`,例如:

```cpp
std::string music_desired = example.array(0).get("hobbies").array(1).get("music");
json::jobject o = json::jobject::parse(R"({"nested":{"x":"1"}})");
std::string x = std::string(o["nested"].as_object()["x"]); // "1"

json::jobject arr = json::jobject::parse(R"([{"id":1},{"id":2}])");
int id = static_cast<int>(arr.array(0).get("id"));
```
See [the full example here](examples/rootarray.cpp).

### A note on booleans
Booleans are handled a bit differently than other data types. Since everything can be cast to a boolean, having an implicit boolean operator meant everything goes to a boolean! Instead, **boolean values are set by using the `set_boolean()` method**. If you do not use this method and instead directly create/assign a boolean to a `jobject` array entry, then the boolean will be cast to an int with a value of 0 or 1. Similarly, you can check if a value is set to true or false using the `is_true()` method.
### 布尔与 null

对 `proxy` 赋布尔值请使用 **`set_boolean(true/false)`**;置空使用 **`set_null()`**。

## API 参考(节选)

| 方法 | 说明 |
| ---- | ---- |
| `static jobject parse(const char* / const std::string&)` | 解析 JSON 文本 |
| `static bool tryparse(const char*, jobject&)` | 解析失败返回 `false` |
| `bool has_key(const std::string&)` / `list_keys()` | 对象模式 |
| `std::string get(key)` / `get(index)` | 取序列化片段 |
| `const_value array(size_t)` | 数组元素(根为数组时) |
| `std::string as_string()` / `pretty()` | 紧凑 / 缩进序列化 |
| `bool operator==(const jobject&)` | 结构相等(槽位比较) |

`entry` / `proxy` 支持到数值类型、`std::string`、`jobject` 及若干 `std::vector<T>` 的转换;细节见 **`json.h`** 注释。

## 项目结构

```
simpleson/
├── json.h / json.cpp # 改造版库源码(CMake 目标 simpleson)
├── start/json.h / start/json.cpp # 原始参考实现(默认不参与构建)
├── CMakeLists.txt
├── README.md
├── 现代C++改造记录.md
├── 任务二.md
├── test/
│ ├── jobject_semantics_test.cpp
│ └── CMakeLists.txt
├── jobject_semantics_test/ # 覆盖率输出等(如 coverage_final)
└── baseline_benchmark_test/
├── baseline_benchmark_test_time.cpp
├── baseline_benchmark_test_time_comprehensive.cpp
├── baseline_benchmark_test_memory.cpp
└── BASELINE_RESULTS.md
```

## 许可

沿用上游 simpleson 的许可。
Loading