From cfb1eacb37c660b80a3a35021d6feafa9561fb09 Mon Sep 17 00:00:00 2001 From: FFMG Date: Sun, 13 Sep 2026 16:22:16 +0200 Subject: [PATCH 1/4] Conform empty input parsing to RFC specifications and bump version to 0.3.0 --- CHANGELOG.md | 10 + README.md | 8 +- src/TinyJSON.cpp | 16 +- src/TinyJSON.h | 7 +- tests/testtinyjson.cpp | 26 +- tests/testtinyjsonexceptions.cpp | 800 ++++++++++++++++++------------- tests/testtinyjsonversion.cpp | 6 +- 7 files changed, 498 insertions(+), 375 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80cd47a..c8d7065 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.0] - 2026-09-13 + +### Changed +- Incremented version to 0.3.0. +- **Breaking Change**: Conformed empty and whitespace-only document parsing to RFC 4627, RFC 7159, RFC 8259, and JSON5 specifications. Parsing empty strings, whitespace-only content, or empty files now returns `nullptr` (or throws `TJParseException` when `throw_exception = true`) instead of fabricating an empty `TJValueString`. +- `TJ::is_valid("")` now correctly returns `false`. + +### Fixed +- Fixed inconsistent behaviour where JSON5 returned `nullptr` on empty input while RFC 7159/8259 returned a `TJValueString`. + ## [0.2.7] - 2026-06-27 ### Added diff --git a/README.md b/README.md index 936f419..e668794 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![CI](https://github.com/FFMG/TinyJSON/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/FFMG/TinyJSON/actions/workflows/c-cpp.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) -[![Version](https://img.shields.io/badge/version-0.2.7-blue.svg)](src/TinyJSON.h) +[![Version](https://img.shields.io/badge/version-0.3.0-blue.svg)](src/TinyJSON.h) A lightweight and lightning-fast C++ JSON & JSON5 parser designed for high performance and minimal footprint. @@ -130,9 +130,9 @@ The version is set in the `TinyJSON.h` file. ```cpp static const short TJ_VERSION_MAJOR = 0; -static const short TJ_VERSION_MINOR = 2; -static const short TJ_VERSION_PATCH = 7; -static const char TJ_VERSION_STRING[] = "0.2.7"; +static const short TJ_VERSION_MINOR = 3; +static const short TJ_VERSION_PATCH = 0; +static const char TJ_VERSION_STRING[] = "0.3.0"; ``` ### Simple Value Access diff --git a/src/TinyJSON.cpp b/src/TinyJSON.cpp index a9a00e1..45b32d5 100644 --- a/src/TinyJSON.cpp +++ b/src/TinyJSON.cpp @@ -4222,15 +4222,16 @@ namespace TinyJSON return nullptr; } - if (value_found == nullptr && parse_options.specification == parse_options::json5_1_0_0) + if (value_found == nullptr) { - // JSON5 requires a value - return nullptr; + parse_result.assign_exception_message(TJCHARPREFIX("The JSON text is empty or contains no value.")); + parse_result.throw_if_exception(); + return nullptr; } if (parse_options.specification == parse_options::rfc4627 && !parse_result.has_exception_message()) { - if (value_found == nullptr || (!value_found->is_array() && !value_found->is_object())) + if (!value_found->is_array() && !value_found->is_object()) { // error: RFC 4627: A JSON text must be either an object or an array. parse_result.assign_exception_message("RFC 4627: A JSON text must be either an object or an array."); @@ -4243,13 +4244,8 @@ namespace TinyJSON } // return if we found anything. - // if we found nothing ... then it is not an error, just an empty string parse_result.throw_if_exception(); - if (value_found == nullptr && parse_result.options().specification == parse_options::json5_1_0_0) - { - return nullptr; - } - return value_found != nullptr ? value_found : new TJValueString(TJCHARPREFIX("")); + return value_found; } /// diff --git a/src/TinyJSON.h b/src/TinyJSON.h index 1eb4ed4..12945e0 100644 --- a/src/TinyJSON.h +++ b/src/TinyJSON.h @@ -49,10 +49,11 @@ // v0.2.5 - added raise warning when key is not found. // v0.2.6 - fixed a bug in the resizing of string buffers. // v0.2.7 - improved parse_file error reporting with specific system errors and file name. +// v0.3.0 - conformed empty input parsing to RFC specifications (returns nullptr / throws instead of empty string). static const short TJ_VERSION_MAJOR = 0; -static const short TJ_VERSION_MINOR = 2; -static const short TJ_VERSION_PATCH = 7; -static const char TJ_VERSION_STRING[] = "0.2.7"; +static const short TJ_VERSION_MINOR = 3; +static const short TJ_VERSION_PATCH = 0; +static const char TJ_VERSION_STRING[] = "0.3.0"; #ifndef TJ_USE_CHAR # define TJ_USE_CHAR 1 diff --git a/tests/testtinyjson.cpp b/tests/testtinyjson.cpp index a8e02b2..8b9a756 100644 --- a/tests/testtinyjson.cpp +++ b/tests/testtinyjson.cpp @@ -352,16 +352,12 @@ TEST(TestBasic, NullInStringIsValid) { delete json; } -TEST(TestBasic, NothingIsJustAnEmptyString) { +TEST(TestBasic, EmptyStringReturnsNull) { auto json = TinyJSON::TJ::parse(""); - ASSERT_NE(nullptr, json); - ASSERT_TRUE(json->is_string()); - ASSERT_STREQ("", json->dump_string()); - - delete json; + ASSERT_EQ(nullptr, json); } -TEST(TestBasic, NothingIsJustAnEmptyStringWithSpaces) { +TEST(TestBasic, WhitespaceOnlyStringReturnsNull) { auto json = TinyJSON::TJ::parse(R"( @@ -369,12 +365,10 @@ TEST(TestBasic, NothingIsJustAnEmptyStringWithSpaces) { -)"); - ASSERT_NE(nullptr, json); - ASSERT_TRUE(json->is_string()); - ASSERT_STREQ("", json->dump_string()); - delete json; + +)"); + ASSERT_EQ(nullptr, json); } TEST(TestBasic, StringValueIsValid) { @@ -563,6 +557,14 @@ TEST(TestBasic, ArrayIsValid) { ASSERT_TRUE(TinyJSON::TJ::is_valid("[12,13,14]")); } +TEST(TestBasic, EmptyStringIsNotValid) { + ASSERT_FALSE(TinyJSON::TJ::is_valid("")); +} + +TEST(TestBasic, WhitespaceOnlyIsNotValid) { + ASSERT_FALSE(TinyJSON::TJ::is_valid(" \t\r\n ")); +} + TEST(TestBasic, UserLiteralsArray) { using namespace TinyJSON; diff --git a/tests/testtinyjsonexceptions.cpp b/tests/testtinyjsonexceptions.cpp index b6193bd..320151b 100644 --- a/tests/testtinyjsonexceptions.cpp +++ b/tests/testtinyjsonexceptions.cpp @@ -1,343 +1,457 @@ -// Licensed to Florent Guelfucci under one or more agreements. -// Florent Guelfucci licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -#include -#define TJ_USE_CHAR 1 -#include "../src/TinyJSON.h" - -TEST(TestException, IfWeHaveNoExceptionWeDoNotThrow) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - TinyJSON::TJValue* json = nullptr; - EXPECT_NO_THROW( json = TinyJSON::TJ::parse("[12,13,14]", options)); - delete json; -} - -TEST(TestException, ParseExceptionMessageIsSetProperly) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - TinyJSON::TJValue* json = nullptr; - EXPECT_NO_THROW(json = TinyJSON::TJ::parse("[12,13,14]", options)); - delete json; -} - -TEST(TestException, SourceCannotBeNull) { - TinyJSON::TJParseException ex("Hello"); - EXPECT_STREQ("Hello", ex.what()); -} - -TEST(TestException, OperatorCopyParseException) { - TinyJSON::TJParseException ex1("Hello"); - TinyJSON::TJParseException ex2("World"); - ex2 = ex1; - EXPECT_STREQ("Hello", ex2.what()); -} - -TEST(TestException, CopyConstructorParseException) { - TinyJSON::TJParseException ex1("Hello"); - TinyJSON::TJParseException ex2(ex1); - EXPECT_STREQ("Hello", ex2.what()); -} - - -TEST(TestException, EscapedTabCharacterInString) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - EXPECT_THROW(TinyJSON::TJ::parse("[\"Tab\tin string\"]", options), TinyJSON::TJParseException); -} - -TEST(TestException, EscapedReturnCharacterInString) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - EXPECT_THROW(TinyJSON::TJ::parse("[\"Return\rin string\"]", options), TinyJSON::TJParseException); -} - -TEST(TestException, EscapedLineFeedCharacterInString) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - EXPECT_THROW(TinyJSON::TJ::parse("[\"Line Feed\nin string\"]", options), TinyJSON::TJParseException); -} - -TEST(TestException, EscapedFormFeedCharacterInString) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - EXPECT_THROW(TinyJSON::TJ::parse("[\"Form Feed\fin string\"]", options), TinyJSON::TJParseException); -} - -TEST(TestException, EscapedBackSpaceCharacterInString) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - EXPECT_THROW(TinyJSON::TJ::parse("[\"BackSpace\bin string\"]", options), TinyJSON::TJParseException); -} - -TEST(TestException, SingleEscapeCharacterInString) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - EXPECT_THROW(TinyJSON::TJ::parse("[\"Single Escape \\ \"]", options), TinyJSON::TJParseException); -} - -TEST(TestException, TheStringIsNotClosed) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - EXPECT_THROW(TinyJSON::TJ::parse("\"Not Closedd", options), TinyJSON::TJParseException); -} - -TEST(TestException, CommaAtTheEndOfObject) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - EXPECT_THROW(TinyJSON::TJ::parse("{\"a\" : 12,}", options), TinyJSON::TJParseException); -} - -TEST(TestException, CommaAtTheEndOfArray) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - EXPECT_THROW(TinyJSON::TJ::parse("[12,]", options), TinyJSON::TJParseException); -} - -TEST(TestException, UnExpectedCommaInObject) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - EXPECT_THROW(TinyJSON::TJ::parse("{,}", options), TinyJSON::TJParseException); -} - -TEST(TestException, UnExpectedCommaInArray) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - EXPECT_THROW(TinyJSON::TJ::parse("[,]", options), TinyJSON::TJParseException); -} - -TEST(TestException, MissingCommaBetweenItemsInObject) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - EXPECT_THROW(TinyJSON::TJ::parse("{\"a\" : 12 \"b\" : 12}", options), TinyJSON::TJParseException); -} - -TEST(TestException, MissingCommaBetweenItemsInArray) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - EXPECT_THROW(TinyJSON::TJ::parse("[12 \"a\"]", options), TinyJSON::TJParseException); -} - -TEST(TestException, UnExpectedCharacterInObject) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - EXPECT_THROW(TinyJSON::TJ::parse("{ % }", options), TinyJSON::TJParseException); -} - -TEST(TestException, UnExpectedEndOfStringWhileParsingObject) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - EXPECT_THROW(TinyJSON::TJ::parse("{", options), TinyJSON::TJParseException); -} - -TEST(TestException, UnExpectedEndOfStringWhileParsingArray) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - EXPECT_THROW(TinyJSON::TJ::parse("[", options), TinyJSON::TJParseException); -} - -TEST(TestException, BadTrue) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - EXPECT_THROW(TinyJSON::TJ::parse("[tru]", options), TinyJSON::TJParseException); -} - -TEST(TestException, BadFalse) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - EXPECT_THROW(TinyJSON::TJ::parse("[fals]", options), TinyJSON::TJParseException); -} - -TEST(TestException, BadNull) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - EXPECT_THROW(TinyJSON::TJ::parse("[nul]", options), TinyJSON::TJParseException); -} - -TEST(TestException, MissingColonInObject) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - EXPECT_THROW(TinyJSON::TJ::parse(R"({ "Missing colon" null })", options), TinyJSON::TJParseException); -} - -TEST(TestException, InvalidNumberWithLeadingZero) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - EXPECT_THROW(TinyJSON::TJ::parse("[0123]", options), TinyJSON::TJParseException); -} - -TEST(TestException, ExponentsWithZeroDoNotThrow) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - TinyJSON::TJValue* json = nullptr; - EXPECT_NO_THROW(json = TinyJSON::TJ::parse("[1e00]", options)); - delete json; -} - -TEST(TestException, WeReachedMaxDepthOfObjects) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - options.max_depth = 4; - TinyJSON::TJValue* json = nullptr; - EXPECT_THROW(json = TinyJSON::TJ::parse(R"({"a":{"b":{"c":{}}}})", options), TinyJSON::TJParseException); - delete json; -} - -TEST(TestException, WeReachedMaxDepthOfArrays) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - options.max_depth = 4; - TinyJSON::TJValue* json = nullptr; - EXPECT_THROW(json = TinyJSON::TJ::parse("[12,[13,[14,[]]]]", options), TinyJSON::TJParseException); - delete json; -} - -TEST(TestException, WeReachedMaxDepthMixed) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - options.max_depth = 4; - TinyJSON::TJValue* json = nullptr; - EXPECT_THROW(json = TinyJSON::TJ::parse(R"({"a":[12,{"c":{}}]})", options), TinyJSON::TJParseException); - delete json; -} - -TEST(TestException, Rfc4627WantsAnObjectOrAnArray) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - options.specification = TinyJSON::parse_options::rfc4627; - TinyJSON::TJValue* json = nullptr; - EXPECT_THROW(json = TinyJSON::TJ::parse("true", options), TinyJSON::TJParseException); - delete json; -} - -TEST(TestException, WeCannotHaveMoreThanOneItemInRoot) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - EXPECT_THROW(TinyJSON::TJ::parse("{},[]", options), TinyJSON::TJParseException); -} - -TEST(TestException, UnexpectedTokenWhileLookingForValue) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - EXPECT_THROW(TinyJSON::TJ::parse(R"({"a" : Value)", options), TinyJSON::TJParseException); -} - -TEST(TestException, ExponentDoesNotHaveANumber) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - EXPECT_THROW(TinyJSON::TJ::parse("[0e]", options), TinyJSON::TJParseException); -} - -TEST(TestException, ExponentDoesNotHaveANumberButHasNegativeSign) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - EXPECT_THROW(TinyJSON::TJ::parse("[0e-]", options), TinyJSON::TJParseException); -} - -TEST(TestException, ExponentDoesNotHaveANumberButHasPositiveSign) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - EXPECT_THROW(TinyJSON::TJ::parse("[0e+]", options), TinyJSON::TJParseException); -} - -TEST(TestException, FractionIsMissingNumberBeforExponent) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - EXPECT_THROW(TinyJSON::TJ::parse("12.e2", options), TinyJSON::TJParseException); -} - -TEST(TestException, FractionIsMissingNumber) { - TinyJSON::parse_options options = {}; - options.throw_exception = true; - EXPECT_THROW(TinyJSON::TJ::parse("12.", options), TinyJSON::TJParseException); -} - -TEST(TestException, EscapedTabCharacterInStringWillLogAndThrow) { - bool called = false; - TinyJSON::parse_options options = {}; - options.throw_exception = true; - options.callback_function = [&](TinyJSON::parse_options::message_type message_type, const TJCHAR* exception_message) { - EXPECT_EQ(message_type, TinyJSON::parse_options::fatal); - EXPECT_TRUE(nullptr != exception_message); - called = true; - }; - EXPECT_THROW(TinyJSON::TJ::parse("[\"Tab\tin string\"]", options), TinyJSON::TJParseException); - EXPECT_TRUE(called); -} - -TEST(TestException, EscapedTabCharacterInStringWillLogAndNotThrow) { - bool called = false; - TinyJSON::parse_options options = {}; - options.throw_exception = false; - options.callback_function = [&](TinyJSON::parse_options::message_type message_type, const TJCHAR* exception_message) { - EXPECT_EQ(message_type, TinyJSON::parse_options::fatal); - EXPECT_TRUE(nullptr != exception_message); - called = true; - }; - EXPECT_NO_THROW(TinyJSON::TJ::parse("[\"Tab\tin string\"]", options)); - EXPECT_TRUE(called); -} - -TEST(TestException, GettingNonExistentKeyWillLogAndNotThrow) { - bool called = false; - TinyJSON::parse_options options = {}; - options.throw_exception = false; - options.strict = true; - options.callback_function = [&](TinyJSON::parse_options::message_type message_type, const TJCHAR* exception_message) { - EXPECT_EQ(message_type, TinyJSON::parse_options::fatal); - EXPECT_STREQ(exception_message, "The key 'b' was not found!"); - called = true; - }; - TinyJSON::TJValue* json = TinyJSON::TJ::parse("{\"a\" : 12}", options); - ASSERT_TRUE(json != nullptr); - auto* object = static_cast(json); - - // try and get a non existent key - const TJCHAR* value = nullptr; - EXPECT_NO_THROW(value = object->get_string("b", true)); - EXPECT_TRUE(called); - EXPECT_STREQ(value, ""); - delete json; -} - -TEST(TestException, ParseFileNotFoundSmarterError) -{ - bool called = false; - std::string error_msg; - TinyJSON::parse_options options = {}; - options.throw_exception = false; - options.callback_function = [&](TinyJSON::parse_options::message_type message_type, const TJCHAR* exception_message) - { - if (message_type == TinyJSON::parse_options::error) - { - called = true; - error_msg = exception_message; - } - }; - TinyJSON::TJ::parse_file("this_file_does_not_exist_xyz.json", options); - EXPECT_TRUE(called); - EXPECT_EQ("File 'this_file_does_not_exist_xyz.json' could not be found!", error_msg); -} - -TEST(TestException, ParseDirectorySmarterError) -{ - bool called = false; - std::string error_msg; - TinyJSON::parse_options options = {}; - options.throw_exception = false; - options.callback_function = [&](TinyJSON::parse_options::message_type message_type, const TJCHAR* exception_message) - { - if (message_type == TinyJSON::parse_options::error) - { - called = true; - error_msg = exception_message; - } - }; - TinyJSON::TJ::parse_file(".", options); - EXPECT_TRUE(called); - EXPECT_NE(error_msg.find("."), std::string::npos); - bool matched = (error_msg == "File '.' is a directory!" || - error_msg == "Access denied to file '.'!" || - error_msg == "File '.' could not be opened!"); - EXPECT_TRUE(matched) << "Actual message: " << error_msg; -} \ No newline at end of file +// Licensed to Florent Guelfucci under one or more agreements. +// Florent Guelfucci licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +#include +#include +#define TJ_USE_CHAR 1 +#include "../src/TinyJSON.h" + +TEST(TestException, IfWeHaveNoExceptionWeDoNotThrow) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + TinyJSON::TJValue* json = nullptr; + EXPECT_NO_THROW( json = TinyJSON::TJ::parse("[12,13,14]", options)); + delete json; +} + +TEST(TestException, ParseExceptionMessageIsSetProperly) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + TinyJSON::TJValue* json = nullptr; + EXPECT_NO_THROW(json = TinyJSON::TJ::parse("[12,13,14]", options)); + delete json; +} + +TEST(TestException, SourceCannotBeNull) { + TinyJSON::TJParseException ex("Hello"); + EXPECT_STREQ("Hello", ex.what()); +} + +TEST(TestException, OperatorCopyParseException) { + TinyJSON::TJParseException ex1("Hello"); + TinyJSON::TJParseException ex2("World"); + ex2 = ex1; + EXPECT_STREQ("Hello", ex2.what()); +} + +TEST(TestException, CopyConstructorParseException) { + TinyJSON::TJParseException ex1("Hello"); + TinyJSON::TJParseException ex2(ex1); + EXPECT_STREQ("Hello", ex2.what()); +} + + +TEST(TestException, EscapedTabCharacterInString) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + EXPECT_THROW(TinyJSON::TJ::parse("[\"Tab\tin string\"]", options), TinyJSON::TJParseException); +} + +TEST(TestException, EscapedReturnCharacterInString) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + EXPECT_THROW(TinyJSON::TJ::parse("[\"Return\rin string\"]", options), TinyJSON::TJParseException); +} + +TEST(TestException, EscapedLineFeedCharacterInString) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + EXPECT_THROW(TinyJSON::TJ::parse("[\"Line Feed\nin string\"]", options), TinyJSON::TJParseException); +} + +TEST(TestException, EscapedFormFeedCharacterInString) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + EXPECT_THROW(TinyJSON::TJ::parse("[\"Form Feed\fin string\"]", options), TinyJSON::TJParseException); +} + +TEST(TestException, EscapedBackSpaceCharacterInString) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + EXPECT_THROW(TinyJSON::TJ::parse("[\"BackSpace\bin string\"]", options), TinyJSON::TJParseException); +} + +TEST(TestException, SingleEscapeCharacterInString) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + EXPECT_THROW(TinyJSON::TJ::parse("[\"Single Escape \\ \"]", options), TinyJSON::TJParseException); +} + +TEST(TestException, TheStringIsNotClosed) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + EXPECT_THROW(TinyJSON::TJ::parse("\"Not Closedd", options), TinyJSON::TJParseException); +} + +TEST(TestException, CommaAtTheEndOfObject) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + EXPECT_THROW(TinyJSON::TJ::parse("{\"a\" : 12,}", options), TinyJSON::TJParseException); +} + +TEST(TestException, CommaAtTheEndOfArray) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + EXPECT_THROW(TinyJSON::TJ::parse("[12,]", options), TinyJSON::TJParseException); +} + +TEST(TestException, UnExpectedCommaInObject) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + EXPECT_THROW(TinyJSON::TJ::parse("{,}", options), TinyJSON::TJParseException); +} + +TEST(TestException, UnExpectedCommaInArray) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + EXPECT_THROW(TinyJSON::TJ::parse("[,]", options), TinyJSON::TJParseException); +} + +TEST(TestException, MissingCommaBetweenItemsInObject) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + EXPECT_THROW(TinyJSON::TJ::parse("{\"a\" : 12 \"b\" : 12}", options), TinyJSON::TJParseException); +} + +TEST(TestException, MissingCommaBetweenItemsInArray) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + EXPECT_THROW(TinyJSON::TJ::parse("[12 \"a\"]", options), TinyJSON::TJParseException); +} + +TEST(TestException, UnExpectedCharacterInObject) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + EXPECT_THROW(TinyJSON::TJ::parse("{ % }", options), TinyJSON::TJParseException); +} + +TEST(TestException, UnExpectedEndOfStringWhileParsingObject) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + EXPECT_THROW(TinyJSON::TJ::parse("{", options), TinyJSON::TJParseException); +} + +TEST(TestException, UnExpectedEndOfStringWhileParsingArray) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + EXPECT_THROW(TinyJSON::TJ::parse("[", options), TinyJSON::TJParseException); +} + +TEST(TestException, BadTrue) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + EXPECT_THROW(TinyJSON::TJ::parse("[tru]", options), TinyJSON::TJParseException); +} + +TEST(TestException, BadFalse) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + EXPECT_THROW(TinyJSON::TJ::parse("[fals]", options), TinyJSON::TJParseException); +} + +TEST(TestException, BadNull) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + EXPECT_THROW(TinyJSON::TJ::parse("[nul]", options), TinyJSON::TJParseException); +} + +TEST(TestException, MissingColonInObject) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + EXPECT_THROW(TinyJSON::TJ::parse(R"({ "Missing colon" null })", options), TinyJSON::TJParseException); +} + +TEST(TestException, InvalidNumberWithLeadingZero) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + EXPECT_THROW(TinyJSON::TJ::parse("[0123]", options), TinyJSON::TJParseException); +} + +TEST(TestException, ExponentsWithZeroDoNotThrow) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + TinyJSON::TJValue* json = nullptr; + EXPECT_NO_THROW(json = TinyJSON::TJ::parse("[1e00]", options)); + delete json; +} + +TEST(TestException, WeReachedMaxDepthOfObjects) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + options.max_depth = 4; + TinyJSON::TJValue* json = nullptr; + EXPECT_THROW(json = TinyJSON::TJ::parse(R"({"a":{"b":{"c":{}}}})", options), TinyJSON::TJParseException); + delete json; +} + +TEST(TestException, WeReachedMaxDepthOfArrays) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + options.max_depth = 4; + TinyJSON::TJValue* json = nullptr; + EXPECT_THROW(json = TinyJSON::TJ::parse("[12,[13,[14,[]]]]", options), TinyJSON::TJParseException); + delete json; +} + +TEST(TestException, WeReachedMaxDepthMixed) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + options.max_depth = 4; + TinyJSON::TJValue* json = nullptr; + EXPECT_THROW(json = TinyJSON::TJ::parse(R"({"a":[12,{"c":{}}]})", options), TinyJSON::TJParseException); + delete json; +} + +TEST(TestException, Rfc4627WantsAnObjectOrAnArray) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + options.specification = TinyJSON::parse_options::rfc4627; + TinyJSON::TJValue* json = nullptr; + EXPECT_THROW(json = TinyJSON::TJ::parse("true", options), TinyJSON::TJParseException); + delete json; +} + +TEST(TestException, WeCannotHaveMoreThanOneItemInRoot) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + EXPECT_THROW(TinyJSON::TJ::parse("{},[]", options), TinyJSON::TJParseException); +} + +TEST(TestException, UnexpectedTokenWhileLookingForValue) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + EXPECT_THROW(TinyJSON::TJ::parse(R"({"a" : Value)", options), TinyJSON::TJParseException); +} + +TEST(TestException, ExponentDoesNotHaveANumber) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + EXPECT_THROW(TinyJSON::TJ::parse("[0e]", options), TinyJSON::TJParseException); +} + +TEST(TestException, ExponentDoesNotHaveANumberButHasNegativeSign) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + EXPECT_THROW(TinyJSON::TJ::parse("[0e-]", options), TinyJSON::TJParseException); +} + +TEST(TestException, ExponentDoesNotHaveANumberButHasPositiveSign) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + EXPECT_THROW(TinyJSON::TJ::parse("[0e+]", options), TinyJSON::TJParseException); +} + +TEST(TestException, FractionIsMissingNumberBeforExponent) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + EXPECT_THROW(TinyJSON::TJ::parse("12.e2", options), TinyJSON::TJParseException); +} + +TEST(TestException, FractionIsMissingNumber) { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + EXPECT_THROW(TinyJSON::TJ::parse("12.", options), TinyJSON::TJParseException); +} + +TEST(TestException, EscapedTabCharacterInStringWillLogAndThrow) { + bool called = false; + TinyJSON::parse_options options = {}; + options.throw_exception = true; + options.callback_function = [&](TinyJSON::parse_options::message_type message_type, const TJCHAR* exception_message) { + EXPECT_EQ(message_type, TinyJSON::parse_options::fatal); + EXPECT_TRUE(nullptr != exception_message); + called = true; + }; + EXPECT_THROW(TinyJSON::TJ::parse("[\"Tab\tin string\"]", options), TinyJSON::TJParseException); + EXPECT_TRUE(called); +} + +TEST(TestException, EscapedTabCharacterInStringWillLogAndNotThrow) { + bool called = false; + TinyJSON::parse_options options = {}; + options.throw_exception = false; + options.callback_function = [&](TinyJSON::parse_options::message_type message_type, const TJCHAR* exception_message) { + EXPECT_EQ(message_type, TinyJSON::parse_options::fatal); + EXPECT_TRUE(nullptr != exception_message); + called = true; + }; + EXPECT_NO_THROW(TinyJSON::TJ::parse("[\"Tab\tin string\"]", options)); + EXPECT_TRUE(called); +} + +TEST(TestException, GettingNonExistentKeyWillLogAndNotThrow) { + bool called = false; + TinyJSON::parse_options options = {}; + options.throw_exception = false; + options.strict = true; + options.callback_function = [&](TinyJSON::parse_options::message_type message_type, const TJCHAR* exception_message) { + EXPECT_EQ(message_type, TinyJSON::parse_options::fatal); + EXPECT_STREQ(exception_message, "The key 'b' was not found!"); + called = true; + }; + TinyJSON::TJValue* json = TinyJSON::TJ::parse("{\"a\" : 12}", options); + ASSERT_TRUE(json != nullptr); + auto* object = static_cast(json); + + // try and get a non existent key + const TJCHAR* value = nullptr; + EXPECT_NO_THROW(value = object->get_string("b", true)); + EXPECT_TRUE(called); + EXPECT_STREQ(value, ""); + delete json; +} + +TEST(TestException, ParseFileNotFoundSmarterError) +{ + bool called = false; + std::string error_msg; + TinyJSON::parse_options options = {}; + options.throw_exception = false; + options.callback_function = [&](TinyJSON::parse_options::message_type message_type, const TJCHAR* exception_message) + { + if (message_type == TinyJSON::parse_options::error) + { + called = true; + error_msg = exception_message; + } + }; + TinyJSON::TJ::parse_file("this_file_does_not_exist_xyz.json", options); + EXPECT_TRUE(called); + EXPECT_EQ("File 'this_file_does_not_exist_xyz.json' could not be found!", error_msg); +} + +TEST(TestException, ParseDirectorySmarterError) +{ + bool called = false; + std::string error_msg; + TinyJSON::parse_options options = {}; + options.throw_exception = false; + options.callback_function = [&](TinyJSON::parse_options::message_type message_type, const TJCHAR* exception_message) + { + if (message_type == TinyJSON::parse_options::error) + { + called = true; + error_msg = exception_message; + } + }; + TinyJSON::TJ::parse_file(".", options); + EXPECT_TRUE(called); + EXPECT_NE(error_msg.find("."), std::string::npos); + bool matched = (error_msg == "File '.' is a directory!" || + error_msg == "Access denied to file '.'!" || + error_msg == "File '.' could not be opened!"); + EXPECT_TRUE(matched) << "Actual message: " << error_msg; +} + +namespace +{ + struct empty_string_callback_tracker + { + bool called = false; + std::string error_msg; + + void operator()(TinyJSON::parse_options::message_type message_type, const TJCHAR* exception_message) + { + if (message_type == TinyJSON::parse_options::fatal || message_type == TinyJSON::parse_options::error) + { + called = true; + if (nullptr != exception_message) + { + error_msg = exception_message; + } + } + } + }; +} + +TEST(TestException, EmptyStringThrowsException) +{ + TinyJSON::parse_options options = {}; + options.throw_exception = true; + EXPECT_THROW(TinyJSON::TJ::parse("", options), TinyJSON::TJParseException); +} + +TEST(TestException, WhitespaceOnlyStringThrowsException) +{ + TinyJSON::parse_options options = {}; + options.throw_exception = true; + EXPECT_THROW(TinyJSON::TJ::parse(" \t\r\n ", options), TinyJSON::TJParseException); +} + +TEST(TestException, EmptyStringThrowsAcrossAllSpecifications) +{ + const enum TinyJSON::parse_options::specification specs[] = { + TinyJSON::parse_options::rfc4627, + TinyJSON::parse_options::rfc7159, + TinyJSON::parse_options::rfc8259, + TinyJSON::parse_options::json5_1_0_0 + }; + + for (const auto spec : specs) + { + TinyJSON::parse_options options = {}; + options.throw_exception = true; + options.specification = spec; + EXPECT_THROW(TinyJSON::TJ::parse("", options), TinyJSON::TJParseException); + } +} + +TEST(TestException, EmptyStringReturnsNullAcrossAllSpecifications) +{ + const enum TinyJSON::parse_options::specification specs[] = { + TinyJSON::parse_options::rfc4627, + TinyJSON::parse_options::rfc7159, + TinyJSON::parse_options::rfc8259, + TinyJSON::parse_options::json5_1_0_0 + }; + + for (const auto spec : specs) + { + TinyJSON::parse_options options = {}; + options.throw_exception = false; + options.specification = spec; + auto* json = TinyJSON::TJ::parse("", options); + EXPECT_EQ(nullptr, json); + } +} + +TEST(TestException, EmptyStringCallbackMessage) +{ + empty_string_callback_tracker tracker; + TinyJSON::parse_options options = {}; + options.throw_exception = false; + options.callback_function = std::ref(tracker); + auto* json = TinyJSON::TJ::parse("", options); + EXPECT_EQ(nullptr, json); + EXPECT_TRUE(tracker.called); + EXPECT_EQ("The JSON text is empty or contains no value.", tracker.error_msg); +} + +TEST(TestException, EmptyFileThrowsException) +{ + const char* empty_filename = "test_empty_file_throws.json"; + { + std::ofstream empty_file(empty_filename); + } + + TinyJSON::parse_options options = {}; + options.throw_exception = true; + EXPECT_THROW(TinyJSON::TJ::parse_file(empty_filename, options), TinyJSON::TJParseException); + + std::remove(empty_filename); +} + +TEST(TestException, EmptyFileReturnsNullWithoutException) +{ + const char* empty_filename = "test_empty_file_null.json"; + { + std::ofstream empty_file(empty_filename); + } + + TinyJSON::parse_options options = {}; + options.throw_exception = false; + auto* json = TinyJSON::TJ::parse_file(empty_filename, options); + EXPECT_EQ(nullptr, json); + + std::remove(empty_filename); +} diff --git a/tests/testtinyjsonversion.cpp b/tests/testtinyjsonversion.cpp index 1c85f6a..0d7d957 100644 --- a/tests/testtinyjsonversion.cpp +++ b/tests/testtinyjsonversion.cpp @@ -12,15 +12,15 @@ TEST(TestVersion, CheckVersionMajor) TEST(TestVersion, CheckVersionMinor) { - ASSERT_EQ(2, TJ_VERSION_MINOR); + ASSERT_EQ(3, TJ_VERSION_MINOR); } TEST(TestVersion, CheckVersionPatch) { - ASSERT_EQ(7, TJ_VERSION_PATCH); + ASSERT_EQ(0, TJ_VERSION_PATCH); } TEST(TestVersion, CheckVersionString) { - ASSERT_STREQ("0.2.7", TJ_VERSION_STRING); + ASSERT_STREQ("0.3.0", TJ_VERSION_STRING); } From 6d5c68e38169bf08c7a852ac8d3ebad057800cf0 Mon Sep 17 00:00:00 2001 From: FFMG Date: Sun, 13 Sep 2026 16:29:29 +0200 Subject: [PATCH 2/4] Changed version update to 0.2.8 --- CHANGELOG.md | 6 +-- GEMINI.md | 77 +++++++++++++++++++++++++++++++++++ README.md | 8 ++-- src/TinyJSON.h | 8 ++-- tests/testtinyjsonversion.cpp | 6 +-- 5 files changed, 91 insertions(+), 14 deletions(-) create mode 100644 GEMINI.md diff --git a/CHANGELOG.md b/CHANGELOG.md index c8d7065..0a4b2a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.3.0] - 2026-09-13 +## [0.2.8] - 2026-09-13 ### Changed -- Incremented version to 0.3.0. +- Incremented version to 0.2.8. - **Breaking Change**: Conformed empty and whitespace-only document parsing to RFC 4627, RFC 7159, RFC 8259, and JSON5 specifications. Parsing empty strings, whitespace-only content, or empty files now returns `nullptr` (or throws `TJParseException` when `throw_exception = true`) instead of fabricating an empty `TJValueString`. - `TJ::is_valid("")` now correctly returns `false`. @@ -32,7 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added warning callback trigger when attempting to retrieve non-existent keys in `TJValueObject` using `try_get_string`, `get_*(..)`, or `get<>(..)` methods. ### Changed -- Incremented version to 0.3.0. +- Incremented version to 0.2.8. ## [0.2.4] - 2026-05-20 diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..6a7e2bf --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,77 @@ +# TinyJSON Project Context + +TinyJSON is a modern, high-performance C++ JSON library designed for performance-critical and resource-constrained applications. It is zero-dependency and supports RFC 8259. + +## General + +- My name is Florent +- I am a c++ developer + +## Project Overview + +- **Main Technologies:** C++ (C++11 for library, C++17 for tests), CMake, GoogleTest. +- **Architecture:** A single-header-ish design (though split into `TinyJSON.h` and `TinyJSON.cpp`) focusing on a fast, minimal-overhead DOM parser. +- **Key Features:** Zero-dependency, minimal memory overhead, supports UTF-8 BOM, customizable via macros (e.g., optional `std::string` or `std::vector` usage). + +## Building and Running + +### Specific to this machine + +- This is a windows 11 machine +- I only have visual studio 2022 + +### Prerequisites +- CMake 3.28 or later. +- A C++ compiler supporting C++17 (for tests). +- Git (to initialize submodules). + +### Commands +```bash +# Initialize submodules (GoogleTest) +git submodule update --init --recursive + +# Configure and build +mkdir build +cd build +cmake .. +cmake --build . + +# Run tests +ctest +``` + +### Linux/GCC Manual Build (Minimal) +```bash +g++ -std=c++11 -Wall -Wextra -Werror -O3 src/TinyJSON.cpp -o tinyjson_lib +``` + +## Development Conventions + +- At the top of each files, add the following + +// Licensed to Florent Guelfucci under one or more agreements. +// Florent Guelfucci licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +- If the above comment is not present, please flag it and ask me if I want to add it. + + +- Use 2 spaces, always, never tabs +- **Performance First:** Performance is the primary goal. "Pretty" code is secondary to execution speed and memory efficiency. +- **Coding Style:** + - **Namespaces/Classes/Structs:** `CamelCase`. + - **Functions/Variables:** `snake_case`. + - **Enums:** `CamelCase` for the type, `lower_case` for values. + - **Constants:** Use `static constexpr` (e.g., `TJ_MAX_DIGITS`) instead of macros where possible for thread safety. + - **Macros:** Used for code repetition (e.g., `TJ_CASE_DIGIT`), named in `UPPERCASE` with `TJ_` prefix. +- **Class Structure:** Organized by visibility: `public`, then `protected`, then `private`. Within each section, variables precede methods. +- **Testing:** New features or bug fixes must include a full set of GoogleTest-based tests in the `tests/` directory. +- Never commit to git unless I ask you +- Unless expicitely asked, make sure the code supports c++11 or later, use MACRO if performance would improve ... but c++ must still be supported. + +## Key Files +- `src/TinyJSON.h`: Main header containing the API and configuration macros. +- `src/TinyJSON.cpp`: Implementation of the parser and writer. +- `tests/`: Extensive test suite using GoogleTest. +- `example/`: Usage examples demonstrating various features like basic parsing, options, and literals. +- `specifications.md`: Detailed information on supported JSON standards. diff --git a/README.md b/README.md index e668794..d116e9e 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![CI](https://github.com/FFMG/TinyJSON/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/FFMG/TinyJSON/actions/workflows/c-cpp.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) -[![Version](https://img.shields.io/badge/version-0.3.0-blue.svg)](src/TinyJSON.h) +[![Version](https://img.shields.io/badge/version-0.2.8-blue.svg)](src/TinyJSON.h) A lightweight and lightning-fast C++ JSON & JSON5 parser designed for high performance and minimal footprint. @@ -130,9 +130,9 @@ The version is set in the `TinyJSON.h` file. ```cpp static const short TJ_VERSION_MAJOR = 0; -static const short TJ_VERSION_MINOR = 3; -static const short TJ_VERSION_PATCH = 0; -static const char TJ_VERSION_STRING[] = "0.3.0"; +static const short TJ_VERSION_MINOR = 2; +static const short TJ_VERSION_PATCH = 8; +static const char TJ_VERSION_STRING[] = "0.2.8"; ``` ### Simple Value Access diff --git a/src/TinyJSON.h b/src/TinyJSON.h index 12945e0..3376abf 100644 --- a/src/TinyJSON.h +++ b/src/TinyJSON.h @@ -49,11 +49,11 @@ // v0.2.5 - added raise warning when key is not found. // v0.2.6 - fixed a bug in the resizing of string buffers. // v0.2.7 - improved parse_file error reporting with specific system errors and file name. -// v0.3.0 - conformed empty input parsing to RFC specifications (returns nullptr / throws instead of empty string). +// v0.2.8 - conformed empty input parsing to RFC specifications (returns nullptr / throws instead of empty string). static const short TJ_VERSION_MAJOR = 0; -static const short TJ_VERSION_MINOR = 3; -static const short TJ_VERSION_PATCH = 0; -static const char TJ_VERSION_STRING[] = "0.3.0"; +static const short TJ_VERSION_MINOR = 2; +static const short TJ_VERSION_PATCH = 8; +static const char TJ_VERSION_STRING[] = "0.2.8"; #ifndef TJ_USE_CHAR # define TJ_USE_CHAR 1 diff --git a/tests/testtinyjsonversion.cpp b/tests/testtinyjsonversion.cpp index 0d7d957..ea2abee 100644 --- a/tests/testtinyjsonversion.cpp +++ b/tests/testtinyjsonversion.cpp @@ -12,15 +12,15 @@ TEST(TestVersion, CheckVersionMajor) TEST(TestVersion, CheckVersionMinor) { - ASSERT_EQ(3, TJ_VERSION_MINOR); + ASSERT_EQ(2, TJ_VERSION_MINOR); } TEST(TestVersion, CheckVersionPatch) { - ASSERT_EQ(0, TJ_VERSION_PATCH); + ASSERT_EQ(8, TJ_VERSION_PATCH); } TEST(TestVersion, CheckVersionString) { - ASSERT_STREQ("0.3.0", TJ_VERSION_STRING); + ASSERT_STREQ("0.2.8", TJ_VERSION_STRING); } From 3b5ca15e1f33b704a64f07d3396d6bbb5ed58482 Mon Sep 17 00:00:00 2001 From: FFMG Date: Sun, 13 Sep 2026 16:46:54 +0200 Subject: [PATCH 3/4] Updated .gitignore --- .gitignore | 6 +++++ GEMINI.md | 77 ------------------------------------------------------ 2 files changed, 6 insertions(+), 77 deletions(-) delete mode 100644 GEMINI.md diff --git a/.gitignore b/.gitignore index 149136d..83778e6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,9 @@ +# AI +.agents/ +.claude/ +.gemini/ +plans/ + # VStudio stuff /.vs /.vscode diff --git a/GEMINI.md b/GEMINI.md deleted file mode 100644 index 6a7e2bf..0000000 --- a/GEMINI.md +++ /dev/null @@ -1,77 +0,0 @@ -# TinyJSON Project Context - -TinyJSON is a modern, high-performance C++ JSON library designed for performance-critical and resource-constrained applications. It is zero-dependency and supports RFC 8259. - -## General - -- My name is Florent -- I am a c++ developer - -## Project Overview - -- **Main Technologies:** C++ (C++11 for library, C++17 for tests), CMake, GoogleTest. -- **Architecture:** A single-header-ish design (though split into `TinyJSON.h` and `TinyJSON.cpp`) focusing on a fast, minimal-overhead DOM parser. -- **Key Features:** Zero-dependency, minimal memory overhead, supports UTF-8 BOM, customizable via macros (e.g., optional `std::string` or `std::vector` usage). - -## Building and Running - -### Specific to this machine - -- This is a windows 11 machine -- I only have visual studio 2022 - -### Prerequisites -- CMake 3.28 or later. -- A C++ compiler supporting C++17 (for tests). -- Git (to initialize submodules). - -### Commands -```bash -# Initialize submodules (GoogleTest) -git submodule update --init --recursive - -# Configure and build -mkdir build -cd build -cmake .. -cmake --build . - -# Run tests -ctest -``` - -### Linux/GCC Manual Build (Minimal) -```bash -g++ -std=c++11 -Wall -Wextra -Werror -O3 src/TinyJSON.cpp -o tinyjson_lib -``` - -## Development Conventions - -- At the top of each files, add the following - -// Licensed to Florent Guelfucci under one or more agreements. -// Florent Guelfucci licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -- If the above comment is not present, please flag it and ask me if I want to add it. - - -- Use 2 spaces, always, never tabs -- **Performance First:** Performance is the primary goal. "Pretty" code is secondary to execution speed and memory efficiency. -- **Coding Style:** - - **Namespaces/Classes/Structs:** `CamelCase`. - - **Functions/Variables:** `snake_case`. - - **Enums:** `CamelCase` for the type, `lower_case` for values. - - **Constants:** Use `static constexpr` (e.g., `TJ_MAX_DIGITS`) instead of macros where possible for thread safety. - - **Macros:** Used for code repetition (e.g., `TJ_CASE_DIGIT`), named in `UPPERCASE` with `TJ_` prefix. -- **Class Structure:** Organized by visibility: `public`, then `protected`, then `private`. Within each section, variables precede methods. -- **Testing:** New features or bug fixes must include a full set of GoogleTest-based tests in the `tests/` directory. -- Never commit to git unless I ask you -- Unless expicitely asked, make sure the code supports c++11 or later, use MACRO if performance would improve ... but c++ must still be supported. - -## Key Files -- `src/TinyJSON.h`: Main header containing the API and configuration macros. -- `src/TinyJSON.cpp`: Implementation of the parser and writer. -- `tests/`: Extensive test suite using GoogleTest. -- `example/`: Usage examples demonstrating various features like basic parsing, options, and literals. -- `specifications.md`: Detailed information on supported JSON standards. From 7e0989d2e172b536e29e976321673cc1d62b4b43 Mon Sep 17 00:00:00 2001 From: FFMG Date: Sun, 13 Sep 2026 16:56:53 +0200 Subject: [PATCH 4/4] Updated ,gitignore again... --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 83778e6..0f73929 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ .claude/ .gemini/ plans/ +GEMINI.md +CLAUDE.md # VStudio stuff /.vs