最初コミット
諏訪子 <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 | +} |
| @@ -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 | +} |