最初コミット

諏訪子 <suwako@076.moe>、2026-06-30 01:26
コミット 0f739e40c671e39f0c21dfbbc2b2310a0a12107a ・ファイルを見る

14個のファイル、+1173 -0

追加 .clangd
@@ -0,0 +1,5 @@
1+CompileFlags:
2+ Add:
3+ - -I./include
4+ - -std=c++20
5+ - -Wno-pragma-once-outside-header
追加 .gitignore
@@ -0,0 +1,2 @@
1+build
2+linbuild
@@ -0,0 +1,94 @@
1+cmake_minimum_required(VERSION 3.14...3.28)
2+project(json LANGUAGES CXX)
3+
4+set(CMAKE_CXX_STANDARD 20)
5+set(CMAKE_CXX_STANDARD_REQUIRED ON)
6+set(CMAKE_CXX_EXTENSIONS OFF)
7+
8+set(BUILD_SHARED_LIBS OFF CACHE BOOL "静的リンク" FORCE)
9+
10+option(ENABLE_TEST "テストプロジェクト" ON)
11+
12+if(MSVC)
13+ set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
14+ add_compile_options(/utf-8)
15+endif()
16+
17+# 本ライブラリ
18+add_library(json STATIC)
19+file(GLOB_RECURSE LIBJSON_SRC CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cc")
20+file(GLOB_RECURSE LIBJSON_INC CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/*.hh")
21+
22+target_sources(json
23+ PRIVATE "${LIBJSON_SRC}"
24+ PUBLIC "${LIBJSON_INC}"
25+)
26+
27+target_include_directories(json PUBLIC
28+ "${CMAKE_CURRENT_SOURCE_DIR}/include"
29+)
30+
31+# テスト用
32+if(ENABLE_TEST)
33+ configure_file(
34+ ${CMAKE_CURRENT_SOURCE_DIR}/tests/webfinger.json
35+ ${CMAKE_CURRENT_BINARY_DIR}/webfinger.json
36+ COPYONLY
37+ )
38+
39+ configure_file(
40+ ${CMAKE_CURRENT_SOURCE_DIR}/tests/activitypub.json
41+ ${CMAKE_CURRENT_BINARY_DIR}/activitypub.json
42+ COPYONLY
43+ )
44+
45+ configure_file(
46+ ${CMAKE_CURRENT_SOURCE_DIR}/tests/gltf.json
47+ ${CMAKE_CURRENT_BINARY_DIR}/gltf.json
48+ COPYONLY
49+ )
50+
51+ add_executable(json_test)
52+ file(GLOB_RECURSE TEST_SRC CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/tests/*.cc")
53+ file(GLOB_RECURSE TEST_INC CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/tests/*.hh")
54+ target_sources(json_test PRIVATE ${TEST_SRC})
55+ target_link_libraries(json_test PRIVATE json)
56+
57+ if(MSVC)
58+ set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT json_test)
59+ endif()
60+endif()
61+
62+# OS
63+if(UNIX)
64+ include(GNUInstallDirs)
65+ set(INSTALL_BINDIR ${CMAKE_INSTALL_LIBDIR})
66+endif()
67+
68+if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
69+ set(PRODUCTION_BUILD OFF CACHE BOOL "リリース版?" FORCE)
70+else()
71+ set(PRODUCTION_BUILD ON CACHE BOOL "リリース版?" FORCE)
72+endif()
73+
74+install(TARGETS json
75+ RUNTIME DESTINATION ${INSTALL_BINDIR}
76+ PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
77+ COMPONENT Runtime
78+)
79+
80+install(DIRECTORY DESTINATION ${INSTALL_DATADIR}
81+ COMPONENT Runtime
82+)
83+
84+if(MSVC)
85+ target_compile_definitions(json PUBLIC _CRT_SECURE_NO_WARNINGS)
86+elseif(MINGW)
87+ if(PRODUCTION_BUILD)
88+ target_link_options(json PRIVATE -mwindows)
89+ endif()
90+elseif(APPLE OR UNIX)
91+ if(PRODUCTION_BUILD)
92+ target_link_options(json PRIVATE -s)
93+ endif()
94+endif()
追加 LICENSE.md
@@ -0,0 +1,21 @@
1+# 076 Free License
2+
3+Copyright (c) テクニカル諏訪子
4+
5+Permission is hereby granted to any person obtaining a copy of the software
6+LibJson (the "Software") to use, modify, merge, copy, publish, distribute,
7+sublicense, and/or sell copies of the Software, subject to the following conditions:
8+
9+ 1. **Origin Attribution**:
10+ - You must not misrepresent the origin of the Software; you must not claim
11+ you created the original Software.
12+ 2. **Notice Preservation**:
13+ - This license and the above copyright notice must remain intact in all copies
14+ of the source code.
15+
16+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
17+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
18+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
20+CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
21+OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
追加 README.md
@@ -0,0 +1,21 @@
1+# LibJSON
2+
3+C++20で作ったミニマリストなJSONライブラリ
4+
5+## インストールする方法
6+### Windowsの場合
7+```powershell
8+> cmake -B build -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=Release
9+> cmake --build build --config Release
10+> mkdir -Force (自分のプロジェクトパス)\deps\libjson\lib
11+> mkdir -Force (自分のプロジェクトパス)\deps\libjson\include
12+> ROBOCOPY .\build\Release\libjson.lib (自分のプロジェクトパス)\deps\libjson\lib
13+> ROBOCOPY .\include (自分のプロジェクトパス)\deps\libjson /E /COPYALL
14+```
15+
16+### Unixの場合
17+```sh
18+$ cmake -B build -DCMAKE_BUILD_TYPE=Release
19+$ cmake --build build --config Release
20+$ cmake --install build --config Release
21+```
@@ -0,0 +1,183 @@
1+/*
2+# 076 Free License
3+
4+Copyright (c) テクニカル諏訪子
5+
6+Permission is hereby granted to any person obtaining a copy of the software
7+LibJson (the "Software") to use, modify, merge, copy, publish, distribute,
8+sublicense, and/or sell copies of the Software, subject to the following conditions:
9+
10+ 1. **Origin Attribution**:
11+ - You must not misrepresent the origin of the Software; you must not claim
12+ you created the original Software.
13+ 2. **Notice Preservation**:
14+ - This license and the above copyright notice must remain intact in all copies
15+ of the source code.
16+
17+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
18+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
19+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
21+CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
22+OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23+*/
24+#ifndef JSON_HH
25+#define JSON_HH
26+
27+#include <util/types.hh>
28+#include <unordered_map>
29+#include <string_view>
30+#include <variant>
31+#include <optional>
32+#include <memory>
33+
34+namespace json {
35+ enum class Error {
36+ None,
37+ SyntaxError,
38+ UnexpectedEnd,
39+ InvalidValue,
40+ InvalidNumber,
41+ UnterminatedString,
42+ }; // enum class Error
43+
44+ struct ParseResult;
45+ struct StringHash;
46+ class Object;
47+ class Value;
48+
49+ struct ParseResult {
50+ std::unique_ptr<Value> value;
51+ Error error = Error::None;
52+ size_t errPos = 0;
53+ bool ok() const { return error == Error::None; }
54+ }; // struct ParseResult
55+
56+ struct StringHash {
57+ using is_transparent = void;
58+ size_t operator()(std::string_view sv) const {
59+ return std::hash<std::string_view>{}(sv);
60+ }
61+ }; // struct StringHash
62+
63+ class Object {
64+ public:
65+ Object() = default;
66+
67+ void insert(string key, Value value);
68+ Value *get(std::string_view key);
69+ const Value *get(std::string_view key) const;
70+ bool contains(std::string_view key) const;
71+
72+ public:
73+ bool empty() const { return m_Data.empty(); }
74+ size_t size() const { return m_Data.size(); }
75+ auto begin() { return m_Data.begin(); }
76+ auto end() { return m_Data.end(); }
77+ auto begin() const { return m_Data.begin(); }
78+ auto end() const { return m_Data.end(); }
79+
80+ private:
81+ using Pair = std::pair<string, Value>;
82+ vector<Pair> m_Data;
83+ std::unordered_map<string, size_t, StringHash, std::equal_to<>> m_Index;
84+ }; // struct Object
85+
86+ class Value {
87+ public:
88+ using Array = vector<Value>;
89+ using Object = json::Object;
90+
91+ using Variant = std::variant<
92+ std::monostate, // null
93+ bool,
94+ f64,
95+ string,
96+ Array,
97+ Object
98+ >;
99+
100+ public:
101+ Value() = default;
102+ Value(std::nullptr_t) : m_Value(std::monostate{}) {}
103+ Value(bool b) : m_Value(b) {}
104+ Value(f64 n) : m_Value(n) {}
105+ Value(f32 n) : m_Value(n) {}
106+ Value(i64 n) : m_Value(static_cast<f64>(n)) {}
107+ Value(i32 n) : m_Value(static_cast<f64>(n)) {}
108+ Value(u64 n) : m_Value(static_cast<f64>(n)) {}
109+ Value(u32 n) : m_Value(static_cast<f64>(n)) {}
110+ Value(string s) : m_Value(std::move(s)) {}
111+ Value(cstr s) : m_Value(string(s)) {}
112+ Value(Array arr) : m_Value(std::move(arr)) {}
113+ Value(Object obj) : m_Value(std::move(obj)) {}
114+
115+ public:
116+ bool is_null() const { return std::holds_alternative<std::monostate>(m_Value); }
117+ bool is_bool() const { return std::holds_alternative<bool>(m_Value); }
118+ bool is_number() const { return std::holds_alternative<f64>(m_Value); }
119+ bool is_string() const { return std::holds_alternative<string>(m_Value); }
120+ bool is_array() const { return std::holds_alternative<Array>(m_Value); }
121+ bool is_object() const { return std::holds_alternative<Object>(m_Value); }
122+
123+ public:
124+ bool as_bool() const { return std::get<bool>(m_Value); }
125+ f64 as_number() const { return std::get<f64>(m_Value); }
126+ string &as_string() { return std::get<string>(m_Value); }
127+ const string &as_string() const { return std::get<string>(m_Value); }
128+ Array &as_array() { return std::get<Array>(m_Value); }
129+ const Array &as_array() const { return std::get<Array>(m_Value); }
130+ Object &as_object() { return std::get<Object>(m_Value); }
131+ const Object &as_object() const { return std::get<Object>(m_Value); }
132+
133+ public:
134+ std::optional<bool> get_bool() const;
135+ std::optional<f64> get_number() const;
136+ std::optional<string> get_string() const;
137+
138+ Value &operator[](size_t index); // 配列
139+ Value &operator[](std::string_view); // オブジェクト
140+
141+ public:
142+ static ParseResult parse(std::string_view jsonText);
143+
144+ public:
145+ string serialize() const;
146+ bool serialize_to_file(const string &path);
147+
148+ private:
149+ static string serialize_number(f64 n);
150+ static string serialize_string(const string &s);
151+ static string serialize_array(const Array &arr);
152+ static string serialize_object(const Object &obj);
153+
154+ private:
155+ Variant m_Value;
156+ }; // class Value
157+
158+ class Parser {
159+ public:
160+ Parser(std::string_view text) : m_Text(text), m_Pos(0) {}
161+
162+ ParseResult parse();
163+ static ParseResult parse_from_file(const string &path);
164+
165+ private:
166+ std::string_view m_Text;
167+ size_t m_Pos;
168+
169+ ParseResult make_error(Error err) const;
170+ void skip_whitespace();
171+ char peek() const;
172+ char consume();
173+ bool expect(char c);
174+
175+ ParseResult parse_value();
176+ ParseResult parse_object();
177+ ParseResult parse_array();
178+ std::optional<string> parse_string();
179+ std::optional<f64> parse_number();
180+ }; // class Parser
181+} // namespace json
182+
183+#endif // JSON_HH
@@ -0,0 +1,32 @@
1+/*
2+# 076 Free License
3+
4+Copyright (c) テクニカル諏訪子
5+
6+Permission is hereby granted to any person obtaining a copy of the software
7+C++ Utils (the "Software") to use, modify, merge, copy, publish, distribute,
8+sublicense, and/or sell copies of the Software, subject to the following conditions:
9+
10+ 1. **Origin Attribution**:
11+ - You must not misrepresent the origin of the Software; you must not claim
12+ you created the original Software.
13+ 2. **Notice Preservation**:
14+ - This license and the above copyright notice must remain intact in all copies
15+ of the source code.
16+
17+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
18+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
19+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
21+CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
22+OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23+*/
24+#ifndef FILE_HH
25+#define FILE_HH
26+
27+#include <util/types.hh>
28+
29+vector<u8> read_file(const string &filename, bool binary = false);
30+bool write_file(const string &filename, vector<string> contents, bool binary = false);
31+
32+#endif // FILE_HH
@@ -0,0 +1,49 @@
1+/*
2+# 076 Free License
3+
4+Copyright (c) テクニカル諏訪子
5+
6+Permission is hereby granted to any person obtaining a copy of the software
7+C++ Utils (the "Software") to use, modify, merge, copy, publish, distribute,
8+sublicense, and/or sell copies of the Software, subject to the following conditions:
9+
10+ 1. **Origin Attribution**:
11+ - You must not misrepresent the origin of the Software; you must not claim
12+ you created the original Software.
13+ 2. **Notice Preservation**:
14+ - This license and the above copyright notice must remain intact in all copies
15+ of the source code.
16+
17+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
18+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
19+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
21+CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
22+OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23+*/
24+#ifndef TYPES_HH
25+#define TYPES_HH
26+
27+#include <cstdint>
28+#include <string>
29+#include <vector>
30+
31+typedef uint8_t u8;
32+typedef uint16_t u16;
33+typedef uint32_t u32;
34+typedef uint64_t u64;
35+
36+typedef int8_t i8;
37+typedef int16_t i16;
38+typedef int32_t i32;
39+typedef int64_t i64;
40+
41+typedef float f32;
42+typedef double f64;
43+
44+typedef const char * cstr;
45+typedef void * any;
46+
47+using std::string;
48+using std::vector;
49+#endif // TYPES_HH
追加 src/file.cc
@@ -0,0 +1,71 @@
1+/*
2+# 076 Free License
3+
4+Copyright (c) テクニカル諏訪子
5+
6+Permission is hereby granted to any person obtaining a copy of the software
7+C++ Utils (the "Software") to use, modify, merge, copy, publish, distribute,
8+sublicense, and/or sell copies of the Software, subject to the following conditions:
9+
10+ 1. **Origin Attribution**:
11+ - You must not misrepresent the origin of the Software; you must not claim
12+ you created the original Software.
13+ 2. **Notice Preservation**:
14+ - This license and the above copyright notice must remain intact in all copies
15+ of the source code.
16+
17+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
18+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
19+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
21+CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
22+OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23+*/
24+#include <util/file.hh>
25+
26+#include <iostream>
27+#include <fstream>
28+
29+vector<u8> read_file(const string &filename, bool binary) {
30+ std::ios::openmode flags = std::ios::in | std::ios::ate;
31+ if (binary) flags |= std::ios::binary;
32+
33+ std::ifstream file(filename, flags);
34+ if (!file) {
35+ throw std::runtime_error("ファイルを開くに失敗: " + filename);
36+ }
37+
38+ auto size = file.tellg();
39+ file.seekg(0, std::ios::beg);
40+ vector<u8> data(static_cast<size_t>(size));
41+ if (!file.read(reinterpret_cast<char *>(data.data()), size)) {
42+ throw std::runtime_error("ファイルの読み込みに失敗。");
43+ }
44+
45+ file.close();
46+
47+ return data;
48+}
49+
50+bool write_file(const string &filename, vector<string> contents, bool binary) {
51+ std::ios::openmode flags;
52+ if (binary) {
53+ flags = std::ios::binary | std::ios::ate;
54+ } else {
55+ flags = std::ios::out | std::ios::trunc;
56+ }
57+
58+ std::ofstream file(filename, flags);
59+ if (!file) {
60+ throw std::runtime_error("ファイルを開くに失敗: " + filename);
61+ }
62+
63+ for (const auto &c : contents) {
64+ file << c;
65+ file << "\n";
66+ }
67+
68+ file.close();
69+
70+ return true;
71+}
追加 src/json.cc
@@ -0,0 +1,353 @@
1+/*
2+# 076 Free License
3+
4+Copyright (c) テクニカル諏訪子
5+
6+Permission is hereby granted to any person obtaining a copy of the software
7+LibJson (the "Software") to use, modify, merge, copy, publish, distribute,
8+sublicense, and/or sell copies of the Software, subject to the following conditions:
9+
10+ 1. **Origin Attribution**:
11+ - You must not misrepresent the origin of the Software; you must not claim
12+ you created the original Software.
13+ 2. **Notice Preservation**:
14+ - This license and the above copyright notice must remain intact in all copies
15+ of the source code.
16+
17+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
18+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
19+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
21+CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
22+OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23+*/
24+#include <suwa/json.hh>
25+#include <util/file.hh>
26+#include <charconv>
27+#include <utility>
28+#include <cassert>
29+#include <sstream>
30+
31+namespace json {
32+ ParseResult Parser::make_error(Error err) const {
33+ return { std::make_unique<Value>(), err, m_Pos };
34+ }
35+
36+ void Parser::skip_whitespace() {
37+ while (m_Pos < m_Text.size()) {
38+ char c = m_Text[m_Pos];
39+ if (c != ' ' && c != '\t' && c != '\n' && c != '\r') break;
40+ ++m_Pos;
41+ }
42+ }
43+
44+ char Parser::peek() const {
45+ return m_Pos < m_Text.size() ? m_Text[m_Pos] : '\0';
46+ }
47+
48+ char Parser::consume() {
49+ return m_Pos < m_Text.size() ? m_Text[m_Pos++] : '\0';
50+ }
51+
52+ bool Parser::expect(char c) {
53+ if (peek() == c) {
54+ consume();
55+ return true;
56+ }
57+
58+ return false;
59+ }
60+
61+ ParseResult Parser::parse_value() {
62+ skip_whitespace();
63+ if (m_Pos >= m_Text.size()) return make_error(Error::UnexpectedEnd);
64+
65+ char c = peek();
66+ if (c == '{') return parse_object();
67+ if (c == '[') return parse_array();
68+ if (c == '"') {
69+ auto str = parse_string();
70+ if (!str) return make_error(Error::UnterminatedString);
71+ return { std::make_unique<Value>(std::move(*str)) };
72+ }
73+
74+ if (m_Text.substr(m_Pos, 4) == "true") {
75+ m_Pos += 4;
76+ return { std::make_unique<Value>(true) };
77+ }
78+
79+ if (m_Text.substr(m_Pos, 5) == "false") {
80+ m_Pos += 5;
81+ return { std::make_unique<Value>(false) };
82+ }
83+
84+ if (m_Text.substr(m_Pos, 4) == "null") {
85+ m_Pos += 4;
86+ return { std::make_unique<Value>(nullptr) };
87+ }
88+
89+ if ((c >= '0' && c <= '9') || c == '-' || c == '+') {
90+ auto num = parse_number();
91+ if (num) return { std::make_unique<Value>(*num) };
92+ return make_error(Error::InvalidNumber);
93+ }
94+
95+ return make_error(Error::InvalidValue);
96+ }
97+
98+ ParseResult Parser::parse_object() {
99+ if (!expect('{')) return make_error(Error::SyntaxError);
100+
101+ Object obj;
102+ skip_whitespace();
103+
104+ if (peek() == '}') {
105+ consume();
106+ return { std::make_unique<Value>(std::move(obj)) };
107+ }
108+
109+ while (true) {
110+ skip_whitespace();
111+ auto keyopt = parse_string();
112+ if (!keyopt) return make_error(Error::SyntaxError);
113+
114+ skip_whitespace();
115+ if (!expect(':')) return make_error(Error::SyntaxError);
116+
117+ auto valres = parse_value();
118+ if (!valres.ok()) return valres;
119+
120+ obj.insert(std::move(*keyopt), std::move(*valres.value));
121+
122+ skip_whitespace();
123+ if (peek() == '}') {
124+ consume();
125+ break;
126+ }
127+ if (!expect(',')) return make_error(Error::SyntaxError);
128+ }
129+
130+ return { std::make_unique<Value>(std::move(obj)) };
131+ }
132+
133+ ParseResult Parser::parse_array() {
134+ if (!expect('[')) return make_error(Error::SyntaxError);
135+
136+ Value::Array arr;
137+ skip_whitespace();
138+
139+ if (peek() == ']') {
140+ consume();
141+ return { std::make_unique<Value>(std::move(arr)) };
142+ }
143+
144+ while (true) {
145+ auto valres = parse_value();
146+ if (!valres.ok()) return valres;
147+ arr.push_back(std::move(*valres.value));
148+
149+ skip_whitespace();
150+ if (peek() == ']') {
151+ consume();
152+ break;
153+ }
154+ if (!expect(',')) return make_error(Error::SyntaxError);
155+ }
156+
157+ return { std::make_unique<Value>(std::move(arr)) };
158+ }
159+
160+ std::optional<string> Parser::parse_string() {
161+ if (!expect('"')) return std::nullopt;
162+
163+ string res;
164+ res.reserve(32);
165+
166+ while (m_Pos < m_Text.size()) {
167+ char c = consume();
168+ if (c == '"') return res;
169+ if (c == '\\') {
170+ if (m_Pos >= m_Text.size()) return std::nullopt;
171+ char esc = consume();
172+ switch (esc) {
173+ case '"': res += '"'; break;
174+ case '\\': res += '\\'; break;
175+ case '/': res += '/'; break;
176+ case 'b': res += '\b'; break;
177+ case 'f': res += '\f'; break;
178+ case 'n': res += '\n'; break;
179+ case 'r': res += '\r'; break;
180+ case 't': res += '\t'; break;
181+ default: res += esc;
182+ }
183+ } else {
184+ res += c;
185+ }
186+ }
187+
188+ return std::nullopt;
189+ }
190+
191+ std::optional<f64> Parser::parse_number() {
192+ cstr start = m_Text.data() + m_Pos;
193+ f64 num = 0.0;
194+ auto [ptr, ec] = std::from_chars(start, m_Text.data() + m_Text.size(), num);
195+
196+ if (ec != std::errc() || ptr == start) return std::nullopt;
197+
198+ m_Pos += (ptr - start);
199+ return num;
200+ }
201+
202+ // 公開API
203+ ParseResult Value::parse(std::string_view jsonText) {
204+ Parser p(jsonText);
205+ return p.parse();
206+ }
207+
208+ ParseResult Parser::parse() {
209+ skip_whitespace();
210+ ParseResult res = parse_value();
211+ if (!res.ok()) return res;
212+
213+ skip_whitespace();
214+ if (m_Pos < m_Text.size()) {
215+ assert(false && "JSON値の後でデータがある");
216+ }
217+
218+ return res;
219+ }
220+
221+ ParseResult Parser::parse_from_file(const string &path) {
222+ vector<u8> data = read_file(path, false);
223+ string content(data.begin(), data.end());
224+ return Parser(content).parse();
225+ }
226+
227+ string Value::serialize() const {
228+ if (is_null()) return "null";
229+ if (is_bool()) return as_bool() ? "true" : "false";
230+ if (is_number()) return serialize_number(as_number());
231+ if (is_string()) return serialize_string(as_string());
232+ if (is_array()) return serialize_array(as_array());
233+ if (is_object()) return serialize_object(as_object());
234+
235+ return "null";
236+ }
237+
238+ bool Value::serialize_to_file(const string &path) {
239+ return write_file(path, { serialize() }, false);
240+ }
241+
242+ string Value::serialize_number(f64 n) {
243+ std::ostringstream oss;
244+ oss << n;
245+ return oss.str();
246+ }
247+
248+ string Value::serialize_string(const string &s) {
249+ string out = "\"";
250+
251+ for (char c : s) {
252+ switch (c) {
253+ case '"': out += "\\\""; break;
254+ case '\\': out += "\\\\"; break;
255+ case '\n': out += "\\n"; break;
256+ case '\r': out += "\\r"; break;
257+ case '\t': out += "\\t"; break;
258+ default: out += c; break;
259+ }
260+ }
261+
262+ out += "\"";
263+ return out;
264+ }
265+
266+ string Value::serialize_array(const Array &arr) {
267+ string out = "[";
268+
269+ for (size_t i = 0; i < arr.size(); ++i) {
270+ if (i > 0) out += ",";
271+ out += arr[i].serialize();
272+ }
273+
274+ out += "]";
275+ return out;
276+ }
277+
278+ string Value::serialize_object(const Object &obj) {
279+ string out = "{";
280+ bool first = true;
281+
282+ for (const auto &[key, val] : obj) {
283+ if (!first) out += ",";
284+ first = false;
285+ out += serialize_string(key);
286+ out += ":";
287+ out += val.serialize();
288+ }
289+
290+ out += "}";
291+ return out;
292+ }
293+
294+ void Object::insert(string key, Value value) {
295+ auto it = m_Index.find(key);
296+ if (it != m_Index.end()) {
297+ m_Data[it->second].second = std::move(value);
298+ } else {
299+ m_Index[key] = m_Data.size();
300+ m_Data.emplace_back(std::move(key), std::move(value));
301+ }
302+ }
303+
304+ Value *Object::get(std::string_view key) {
305+ auto it = m_Index.find(key);
306+ return it != m_Index.end() ? &m_Data[it->second].second : nullptr;
307+ }
308+
309+ const Value *Object::get(std::string_view key) const {
310+ auto it = m_Index.find(key);
311+ return it != m_Index.end() ? &m_Data[it->second].second : nullptr;
312+ }
313+
314+ bool Object::contains(std::string_view key) const {
315+ return m_Index.contains(key);
316+ }
317+
318+ //////////////
319+
320+ std::optional<bool> Value::get_bool() const {
321+ if (auto *b = std::get_if<bool>(&m_Value)) return *b;
322+ return std::nullopt;
323+ }
324+
325+ std::optional<f64> Value::get_number() const {
326+ if (auto *b = std::get_if<f64>(&m_Value)) return *b;
327+ return std::nullopt;
328+ }
329+
330+ std::optional<string> Value::get_string() const {
331+ if (auto *b = std::get_if<string>(&m_Value)) return *b;
332+ return std::nullopt;
333+ }
334+
335+ Value &Value::operator[](size_t index) {
336+ if (!is_array()) as_array() = Array{};
337+
338+ auto &arr = as_array();
339+ if (index >= arr.size()) arr.resize(index + 1);
340+ return arr[index];
341+ }
342+
343+ Value &Value::operator[](std::string_view key) {
344+ if (!is_object()) as_object() = Object{};
345+
346+ auto *val = as_object().get(key);
347+ if (!val) {
348+ as_object().insert(string(key), Value{});
349+ val = as_object().get(key);
350+ }
351+ return *val;
352+ }
353+} // namespace json
@@ -0,0 +1,15 @@
1+{
2+ "@context": [
3+ "https://www.w3.org/ns/activitystreams",
4+ "https://w3id.org/security/v1"
5+ ],
6+ "type": "Person",
7+ "id": "https://example.social/users/sam",
8+ "preferredUsername": "sam",
9+ "inbox": "https://example.social/inbox",
10+ "publicKey": {
11+ "id": "https://example.social/users/sam#main-key",
12+ "owner": "https://example.social/users/sam",
13+ "publicKeyPem": "-----BEGIN PUBLIC KEY-----\n...\n----END PUBLIC KEY----"
14+ }
15+}
@@ -0,0 +1,70 @@
1+{
2+ "scene": 0,
3+ "scenes" : [
4+ {
5+ "nodes" : [ 0 ]
6+ }
7+ ],
8+
9+ "nodes" : [
10+ {
11+ "mesh" : 0
12+ }
13+ ],
14+
15+ "meshes" : [
16+ {
17+ "primitives" : [ {
18+ "attributes" : {
19+ "POSITION" : 1
20+ },
21+ "indices" : 0
22+ } ]
23+ }
24+ ],
25+
26+ "buffers" : [
27+ {
28+ "uri" : "data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA=",
29+ "byteLength" : 44
30+ }
31+ ],
32+ "bufferViews" : [
33+ {
34+ "buffer" : 0,
35+ "byteOffset" : 0,
36+ "byteLength" : 6,
37+ "target" : 34963
38+ },
39+ {
40+ "buffer" : 0,
41+ "byteOffset" : 8,
42+ "byteLength" : 36,
43+ "target" : 34962
44+ }
45+ ],
46+ "accessors" : [
47+ {
48+ "bufferView" : 0,
49+ "byteOffset" : 0,
50+ "componentType" : 5123,
51+ "count" : 3,
52+ "type" : "SCALAR",
53+ "max" : [ 2 ],
54+ "min" : [ 0 ]
55+ },
56+ {
57+ "bufferView" : 1,
58+ "byteOffset" : 0,
59+ "componentType" : 5126,
60+ "count" : 3,
61+ "type" : "VEC3",
62+ "max" : [ 1.0, 1.0, 0.0 ],
63+ "min" : [ 0.0, 0.0, 0.0 ]
64+ }
65+ ],
66+
67+ "asset" : {
68+ "version" : "2.0"
69+ }
70+}
追加 tests/main.cc
@@ -0,0 +1,247 @@
1+#include <cassert>
2+#include <iostream>
3+#include <suwa/json.hh>
4+#include <util/file.hh>
5+
6+int main(void) {
7+ {
8+ const string src = R"({
9+ "name": "諏訪子",
10+ "kero": "🐸",
11+ "age": 2300,
12+ "alive": true,
13+ "power": 99.5,
14+ "items": ["hat", "frog", 42],
15+ "nested": {
16+ "faith": null
17+ }
18+ })";
19+
20+ auto res = json::Parser(src).parse();
21+ assert(res.error == json::Error::None);
22+ assert(res.value);
23+ assert(res.value->is_object());
24+
25+ auto &root = res.value->as_object();
26+ assert(root.get("name")->as_string() == "諏訪子");
27+ assert(root.get("kero")->as_string() == "🐸");
28+ assert(root.get("age")->as_number() == 2300);
29+ assert(root.get("alive")->as_bool() == true);
30+ assert(root.get("power")->as_number() == 99.5);
31+
32+ auto &items = root.get("items")->as_array();
33+ assert(items.size() == 3);
34+ assert(items[0].as_string() == "hat");
35+ assert(items[1].as_string() == "frog");
36+ assert(items[2].as_number() == 42);
37+
38+ auto &nested = root.get("nested")->as_object();
39+ assert(nested.get("faith")->is_null());
40+
41+ auto out = res.value->serialize();
42+ std::cout << out << std::endl;
43+
44+ auto res2 = json::Parser(out).parse();
45+ assert(res2.error == json::Error::None);
46+
47+ std::cout << "Memory OK" << std::endl;
48+ }
49+
50+ {
51+ auto res = json::Parser::parse_from_file("webfinger.json");
52+ assert(res.error == json::Error::None);
53+ assert(res.value);
54+ assert(res.value->is_object());
55+
56+ auto &root = res.value->as_object();
57+ auto *links = root.get("links");
58+ assert(links);
59+ assert(links->is_array());
60+
61+ auto &arr = links->as_array();
62+ assert(arr.size() == 1);
63+ assert(arr[0].is_object());
64+ auto &link = arr[0].as_object();
65+
66+ assert(link.get("href"));
67+ assert(link.get("rel"));
68+ assert(link.get("type"));
69+
70+ assert(link.get("href")->as_string() == "https://sns.076.moe/users/suwako");
71+ assert(link.get("rel")->as_string() == "self");
72+ assert(link.get("type")->as_string() == "application/activity+json");
73+
74+ auto *subject = root.get("subject");
75+ assert(subject);
76+ assert(subject->as_string() == "acct:suwako@sns.076.moe");
77+
78+ auto out = res.value->serialize_to_file("webfinger2.json");
79+
80+ auto res2 = json::Parser::parse_from_file("webfinger2.json");
81+ assert(res2.error == json::Error::None);
82+
83+ std::cout << "WebFinger OK" << std::endl;
84+ }
85+
86+ {
87+ auto res = json::Parser::parse_from_file("gltf.json");
88+ assert(res.error == json::Error::None);
89+ assert(res.value);
90+ assert(res.value->is_object());
91+
92+ auto &root = res.value->as_object();
93+ assert(root.get("scene")->as_number() == 0);
94+
95+ {
96+ auto *scenes = root.get("scenes");
97+ assert(scenes);
98+ assert(scenes->is_array());
99+
100+ auto &arr = scenes->as_array();
101+ assert(arr.size() == 1);
102+ assert(arr[0].is_object());
103+ auto &scene = arr[0].as_object();
104+
105+ assert(scene.get("nodes")->is_array());
106+ auto &sceneNodes = scene.get("nodes")->as_array();
107+ assert(sceneNodes.size() == 1);
108+ assert(sceneNodes[0].is_number() == 0);
109+ }
110+
111+ {
112+ auto *nodes = root.get("nodes");
113+ auto &arr = nodes->as_array();
114+ assert(arr.size() == 1);
115+ assert(arr[0].is_object());
116+ auto &node = arr[0].as_object();
117+
118+ assert(node.get("mesh")->is_number() == 0);
119+ }
120+
121+ {
122+ auto *buffers = root.get("buffers");
123+ auto &arr = buffers->as_array();
124+ assert(arr.size() == 1);
125+ assert(arr[0].is_object());
126+ assert(arr[0].as_object().get("uri")->as_string() == "data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA");
127+ assert(arr[0].as_object().get("byteLength")->as_number() == 44);
128+ }
129+
130+ {
131+ auto *bufferViews = root.get("bufferViews");
132+ auto &arr = bufferViews->as_array();
133+ assert(arr.size() == 2);
134+ assert(arr[0].is_object());
135+ assert(arr[0].as_object().get("buffer")->as_number() == 0);
136+ assert(arr[0].as_object().get("byteOffset")->as_number() == 0);
137+ assert(arr[0].as_object().get("byteLength")->as_number() == 6);
138+ assert(arr[0].as_object().get("target")->as_number() == 34963);
139+
140+ assert(arr[1].as_object().get("buffer")->as_number() == 0);
141+ assert(arr[1].as_object().get("byteOffset")->as_number() == 8);
142+ assert(arr[1].as_object().get("byteLength")->as_number() == 36);
143+ assert(arr[1].as_object().get("target")->as_number() == 34962);
144+ }
145+
146+ {
147+ auto *accessors = root.get("accessors");
148+ auto &arr = accessors->as_array();
149+ assert(arr.size() == 2);
150+ assert(arr[0].is_object());
151+ assert(arr[0].as_object().get("bufferView")->as_number() == 0);
152+ assert(arr[0].as_object().get("byteOffset")->as_number() == 0);
153+ assert(arr[0].as_object().get("componentType")->as_number() == 5123);
154+ assert(arr[0].as_object().get("count")->as_number() == 3);
155+ assert(arr[0].as_object().get("type")->as_string() == "SCALAR");
156+ auto max0 = arr[0].as_object().get("max")->as_array();
157+ assert(max0.size() == 1);
158+ assert(max0[0].get_number() == 2);
159+ auto min0 = arr[0].as_object().get("min")->as_array();
160+ assert(min0.size() == 1);
161+ assert(min0[0].get_number() == 0);
162+
163+ assert(arr[1].is_object());
164+ assert(arr[1].as_object().get("bufferView")->as_number() == 1);
165+ assert(arr[1].as_object().get("byteOffset")->as_number() == 0);
166+ assert(arr[1].as_object().get("componentType")->as_number() == 5126);
167+ assert(arr[1].as_object().get("count")->as_number() == 3);
168+ assert(arr[1].as_object().get("type")->as_string() == "VEC3");
169+ auto max1 = arr[1].as_object().get("max")->as_array();
170+ assert(max1.size() == 3);
171+ assert(max1[0].get_number() == 1.0);
172+ assert(max1[1].get_number() == 1.0);
173+ assert(max1[2].get_number() == 0.0);
174+ auto min1 = arr[1].as_object().get("min")->as_array();
175+ assert(min1.size() == 3);
176+ assert(min1[0].get_number() == 0.0);
177+ assert(min1[1].get_number() == 0.0);
178+ assert(min1[2].get_number() == 0.0);
179+ }
180+
181+ {
182+ auto *asset = root.get("asset");
183+ auto &obj = asset->as_object();
184+ assert(obj.get("version")->as_string() == "2.0");
185+ }
186+
187+ auto out = res.value->serialize_to_file("gltf2.json");
188+
189+ auto res2 = json::Parser::parse_from_file("gltf2.json");
190+ assert(res2.error == json::Error::None);
191+
192+ std::cout << "glTF OK" << std::endl;
193+ }
194+
195+ {
196+ auto res = json::Parser::parse_from_file("activitypub.json");
197+ assert(res.error == json::Error::None);
198+ assert(res.value);
199+ assert(res.value->is_object());
200+
201+ auto &root = res.value->as_object();
202+ auto *context = root.get("@context");
203+ assert(context);
204+ assert(context->is_array());
205+
206+ auto &arr = context->as_array();
207+ assert(arr.size() == 2);
208+ assert(arr[0].is_string());
209+ assert(arr[1].is_string());
210+ assert(arr[0].get_string() == "https://www.w3.org/ns/activitystreams");
211+ assert(arr[1].get_string() == "https://w3id.org/security/v1");
212+
213+ auto *type = root.get("type");
214+ assert(type);
215+ assert(type->as_string() == "Person");
216+
217+ auto *id = root.get("id");
218+ assert(id);
219+ assert(id->as_string() == "https://example.social/users/sam");
220+
221+ auto *preferredUsername = root.get("preferredUsername");
222+ assert(preferredUsername);
223+ assert(preferredUsername->as_string() == "sam");
224+
225+ auto *inbox = root.get("inbox");
226+ assert(inbox);
227+ assert(inbox->as_string() == "https://example.social/inbox");
228+
229+ auto *publicKey = root.get("publicKey");
230+ assert(publicKey);
231+ assert(publicKey->is_object());
232+
233+ auto &pubkey = publicKey->as_object();
234+ assert(pubkey.get("id")->as_string() == "https://example.social/users/sam#main-key");
235+ assert(pubkey.get("owner")->as_string() == "https://example.social/users/sam");
236+ assert(pubkey.get("publicKeyPem")->as_string() == "-----BEGIN PUBLIC KEY-----\n...\n----END PUBLIC KEY----");
237+
238+ auto out = res.value->serialize_to_file("activitypub2.json");
239+
240+ auto res2 = json::Parser::parse_from_file("activitypub2.json");
241+ assert(res2.error == json::Error::None);
242+
243+ std::cout << "ActivityPub OK" << std::endl;
244+ }
245+
246+ return 0;
247+}
@@ -0,0 +1,10 @@
1+{
2+ "links": [
3+ {
4+ "href": "https://sns.076.moe/users/suwako",
5+ "rel": "self",
6+ "type": "application/activity+json"
7+ }
8+ ],
9+ "subject": "acct:suwako@sns.076.moe"
10+}