From 265ff852c408f4ed0d1439d857366b2bdc5770f2 Mon Sep 17 00:00:00 2001 From: Blake-Madden <66873089+Blake-Madden@users.noreply.github.com> Date: Tue, 2 Sep 2025 17:58:59 -0400 Subject: [PATCH 01/12] Add API for property accessors Deprecate older function names #closes #9 --- src/wxSimpleJSON.cpp | 36 ++++++++-------- src/wxSimpleJSON.h | 100 ++++++++++++++++++++++++++++++++----------- 2 files changed, 94 insertions(+), 42 deletions(-) diff --git a/src/wxSimpleJSON.cpp b/src/wxSimpleJSON.cpp index 653ff87..c7e40b6 100644 --- a/src/wxSimpleJSON.cpp +++ b/src/wxSimpleJSON.cpp @@ -126,7 +126,7 @@ wxSimpleJSON::Ptr_t wxSimpleJSON::Item(size_t index) const return Create(item); } -wxString wxSimpleJSON::GetValueString(const wxString &defaultValue, +wxString wxSimpleJSON::AsString(const wxString &defaultValue, const wxMBConv &conv) const { if(!m_d || (m_d->type != cJSON_String)) { @@ -135,8 +135,8 @@ wxString wxSimpleJSON::GetValueString(const wxString &defaultValue, return wxString(m_d->valuestring, conv); } -wxArrayString wxSimpleJSON::GetValueArrayString(const wxMBConv &conv) const -{ +wxArrayString wxSimpleJSON::AsArrayString(const wxMBConv &conv) const + { if(!m_d || (m_d->type != cJSON_Array)) { return wxArrayString(); } @@ -144,13 +144,13 @@ wxArrayString wxSimpleJSON::GetValueArrayString(const wxMBConv &conv) const wxArrayString arr; wxSimpleJSON::Ptr_t parr = Create(m_d); for(size_t i = 0; i < parr->ArraySize(); ++i) { - arr.Add(parr->Item(i)->GetValueString(wxEmptyString, conv)); + arr.Add(parr->Item(i)->AsString(wxEmptyString, conv)); } return arr; } -std::vector wxSimpleJSON::GetValueStringVector(const wxMBConv& conv) const -{ +std::vector wxSimpleJSON::AsStrings(const wxMBConv& conv) const + { if (!m_d || (m_d->type != cJSON_Array)) { return std::vector(); } @@ -158,13 +158,13 @@ std::vector wxSimpleJSON::GetValueStringVector(const wxMBConv& conv) c std::vector arr; wxSimpleJSON::Ptr_t parr = Create(m_d); for (size_t i = 0; i < parr->ArraySize(); ++i) { - arr.emplace_back(parr->Item(i)->GetValueString(wxEmptyString, conv)); + arr.emplace_back(parr->Item(i)->AsString(wxEmptyString, conv)); } return arr; } -std::vector wxSimpleJSON::GetValueArrayObject() const -{ +std::vector wxSimpleJSON::AsNodes() const + { if(!m_d || (m_d->type != cJSON_Array)) { return std::vector(); } @@ -177,16 +177,16 @@ std::vector wxSimpleJSON::GetValueArrayObject() const return arr; } -double wxSimpleJSON::GetValueNumber(double defaultValue) const -{ +double wxSimpleJSON::AsDouble(double defaultValue) const + { if(!m_d || (m_d->type != cJSON_Number)) { return defaultValue; } return m_d->valuedouble; } -std::vector wxSimpleJSON::GetValueArrayNumber(double defaultValue) const -{ +std::vector wxSimpleJSON::AsDoubles(double defaultValue) const + { if (!m_d || (m_d->type != cJSON_Array)) { return std::vector(); } @@ -194,7 +194,7 @@ std::vector wxSimpleJSON::GetValueArrayNumber(double defaultValue) const std::vector arr; wxSimpleJSON::Ptr_t parr = Create(m_d); for (size_t i = 0; i < parr->ArraySize(); ++i) { - arr.emplace_back(parr->Item(i)->GetValueNumber(defaultValue)); + arr.emplace_back(parr->Item(i)->AsDouble(defaultValue)); } return arr; } @@ -233,7 +233,7 @@ wxSimpleJSON &wxSimpleJSON::Add(const wxString &name, bool value) return *this; } -bool wxSimpleJSON::GetValueBool(bool defaultValue) const +bool wxSimpleJSON::AsBool(bool defaultValue) const { if(!m_d || (m_d->type != cJSON_True && m_d->type != cJSON_False)) { return defaultValue; @@ -242,8 +242,8 @@ bool wxSimpleJSON::GetValueBool(bool defaultValue) const return m_d->type == cJSON_True; } -std::vector wxSimpleJSON::GetValueArrayBool(bool defaultValue) const -{ +std::vector wxSimpleJSON::AsBools(bool defaultValue) const + { if (!m_d || (m_d->type != cJSON_Array)) { return std::vector(); } @@ -251,7 +251,7 @@ std::vector wxSimpleJSON::GetValueArrayBool(bool defaultValue) const std::vector arr; wxSimpleJSON::Ptr_t parr = Create(m_d); for (size_t i = 0; i < parr->ArraySize(); ++i) { - arr.push_back(parr->Item(i)->GetValueBool(defaultValue)); + arr.push_back(parr->Item(i)->AsBool(defaultValue)); } return arr; } diff --git a/src/wxSimpleJSON.h b/src/wxSimpleJSON.h index a648d95..42db382 100644 --- a/src/wxSimpleJSON.h +++ b/src/wxSimpleJSON.h @@ -216,80 +216,132 @@ class JSON_API_EXPORT wxSimpleJSON * string failed. * @param conv How to encode the value while reading it. * @return The string value that was read, or empty string upon failure. - * @note Call GetType() to verify the node's data type to ensure that - * you are calling the correct @c GetValue___() function. + * @note Call IsValueString() to verify the node's data type + * is a string. */ - wxString GetValueString(const wxString &defaultValue = wxEmptyString, + wxString AsString(const wxString &defaultValue = wxEmptyString, const wxMBConv &conv = wxConvUTF8) const; + /// @deprecated Use AsString() instead. + [[deprecated("Use AsString() instead")]] + wxString GetValueString(const wxString& defaultValue = wxEmptyString, + const wxMBConv& conv = wxConvUTF8) const + { + return AsString(defaultValue, conv); + } + /** * @brief Return the node's value as a boolean (if its type is JSONType::IS_FALSE or JSONType::IS_TRUE). * @param defaultValue The value to return if reading it as a * boolean failed. * @return The value as a boolean. - * @note Call GetType() to verify the node's data type to ensure that - * you are calling the correct @c GetValue___() function. + * @note Call IsValueBoolean() to verify the node's data type + * is a boolean. */ - bool GetValueBool(bool defaultValue = false) const; + bool AsBool(bool defaultValue = false) const; + + /// @deprecated Use AsBool() instead. + [[deprecated("Use AsBool() instead")]] + bool GetValueBool(bool defaultValue = false) const { return AsBool(defaultValue); } /** * @brief Returns the node's values as an array of booleans (if its type is JSONType::IS_TRUE/IS_FALSE and the array's values are boolean). * @param defaultValue The value to return upon failure. * @return The node's values as a vector of booleans. - * @note Call GetType() to verify the node's data type to ensure that - * you are calling the correct @c GetValue___() function. + * @note Call IsValueArray() to verify the node's data type + * is an array. */ - std::vector GetValueArrayBool(bool defaultValue = false) const; + std::vector AsBools(bool defaultValue = false) const; + + /// @deprecated Use AsBools() instead. + [[deprecated("Use AsBools() instead")]] + std::vector GetValueArrayBool(bool defaultValue = false) const + { + return AsBools(defaultValue); + } /** * @brief Returns the node's values as an array of strings (if its type is JSONType::IS_ARRAY). * @param conv How to encode the values while reading them. * @return The node's values as a string array. - * @note Call GetType() to verify the node's data type to ensure that - * you are calling the correct @c GetValue___() function. + * @note Call IsValueArray() to verify the node's data type + * is an array. */ - wxArrayString GetValueArrayString(const wxMBConv &conv = wxConvUTF8) const; + wxArrayString AsArrayString(const wxMBConv &conv = wxConvUTF8) const; + + /// @deprecated Use AsArrayString() instead. + [[deprecated("Use AsArrayString() instead")]] + wxArrayString GetValueArrayString(const wxMBConv& conv = wxConvUTF8) const + { + return AsStrings(conv); + } /** * @brief Returns the node's values as a vector of strings (if its type is JSONType::IS_ARRAY). * @param conv How to encode the values while reading them. * @return The node's values as a string vector. - * @note Call GetType() to verify the node's data type to ensure that - * you are calling the correct @c GetValue___() function. + * @note Call IsValueArray() to verify the node's data type + * is an array. */ - std::vector GetValueStringVector(const wxMBConv& conv = wxConvUTF8) const; + std::vector AsStrings(const wxMBConv& conv = wxConvUTF8) const; + + /// @deprecated Use AsStrings() instead. + [[deprecated("Use AsStrings() instead")]] + std::vector GetValueStringVector(const wxMBConv& conv = wxConvUTF8) const + { + return AsStrings(conv); + } + /** * @brief Returns the node's values as an array of nodes. (if its type is JSONType::IS_ARRAY). * @param conv How to encode the values while reading them. * @return The node's values as a string array. - * @note Call GetType() to verify the node's data type to ensure that - * you are calling the correct @c GetValue___() function. + * @note Call IsValueArray() to verify the node's data type + * is an array. */ - std::vector GetValueArrayObject() const; + std::vector AsNodes() const; + + /// @deprecated Use AsNodes() instead. + [[deprecated("Use AsNodes() instead")]] + std::vector GetValueArrayObject() const { return AsNodes(); } /** * @brief Returns the node's value as a double * (if its type is JSONType::IS_NUMBER). * @param defaultValue The value to return upon failure. - * @note Call GetType() to verify the node's data type to ensure that - * you are calling the correct @c GetValue___() function. + * @note Call IsValueNumber() to verify the node's data type + * is a number. */ - double GetValueNumber(double defaultValue = -1) const; + double AsDouble(double defaultValue = -1) const; + + /// @deprecated Use AsDouble() instead. + [[deprecated("Use AsDouble() instead")]] + double GetValueNumber(double defaultValue = -1) const + { + return AsDouble(defaultValue); + } /** * @brief Returns the node's values as an array of numbers (if its type is JSONType::IS_ARRAY and the array's values are numeric). * @param defaultValue The value to return upon failure. * @return The node's values as a vector of numbers. - * @note Call GetType() to verify the node's data type to ensure that - * you are calling the correct @c GetValue___() function. + * @note Call IsValueArray() to verify the node's data type + * is an array. */ - std::vector GetValueArrayNumber(double defaultValue = -1) const; + std::vector AsDoubles(double defaultValue = -1) const; + + /// @deprecated Use AsDoubles() instead. + [[deprecated("Use AsDoubles() instead")]] + std::vector GetValueArrayNumber(double defaultValue = -1) const + { + return AsDoubles(defaultValue); + } /** * @brief Returns a node's property (by name). From 9b89e85d5d0acac45b7ae104b015bf2f285d02c3 Mon Sep 17 00:00:00 2001 From: Blake-Madden <66873089+Blake-Madden@users.noreply.github.com> Date: Tue, 9 Sep 2025 18:07:28 -0400 Subject: [PATCH 02/12] Improve docs --- src/wxSimpleJSON.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/wxSimpleJSON.h b/src/wxSimpleJSON.h index 42db382..880e827 100644 --- a/src/wxSimpleJSON.h +++ b/src/wxSimpleJSON.h @@ -359,6 +359,8 @@ class JSON_API_EXPORT wxSimpleJSON /** * @brief Deletes a property with a given zero-based array index. + * @details This only applies to properties containing an array + * (of either values or other nodes). * @param idx The index of the property to delete. * @return @c true if the property was successfully deleted. */ From bbcf6ed9e2583dd8b16b2f370745c44f7885673b Mon Sep 17 00:00:00 2001 From: Blake-Madden <66873089+Blake-Madden@users.noreply.github.com> Date: Tue, 9 Sep 2025 18:08:52 -0400 Subject: [PATCH 03/12] Use wxString instead of wxFileName parameters --- src/wxSimpleJSON.cpp | 14 +++++++------- src/wxSimpleJSON.h | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/wxSimpleJSON.cpp b/src/wxSimpleJSON.cpp index c7e40b6..31bd114 100644 --- a/src/wxSimpleJSON.cpp +++ b/src/wxSimpleJSON.cpp @@ -282,8 +282,8 @@ wxSimpleJSON::Ptr_t wxSimpleJSON::Create(const wxString &buffer, bool isRoot, co } } // get the text where the error occurred - wxString errorLine(startOfErrorLine, conv, endOfErrorLine-startOfErrorLine); - wxString errorLineStartOfError(parseEnd, conv, endOfErrorLine-parseEnd); + const wxString errorLine(startOfErrorLine, conv, endOfErrorLine-startOfErrorLine); + const wxString errorLineStartOfError(parseEnd, conv, endOfErrorLine-parseEnd); parsedNode->SetLastError( wxString::Format(_(L"JSON parsing error at line %s, column %s.\n\n" "full line:\n%s\n\n" @@ -297,12 +297,12 @@ wxSimpleJSON::Ptr_t wxSimpleJSON::Create(const wxString &buffer, bool isRoot, co return parsedNode; } -wxSimpleJSON::Ptr_t wxSimpleJSON::LoadFile(const wxFileName &filename, const wxMBConv &conv) +wxSimpleJSON::Ptr_t wxSimpleJSON::LoadFile(const wxString &filename, const wxMBConv &conv) { - if(!filename.Exists()) { + if (!wxFileName{ filename }.Exists()) { return Create(nullptr); } - wxFFile fp(filename.GetFullPath(), "rb"); + wxFFile fp(filename, "rb"); wxString content; if(fp.IsOpened() && fp.ReadAll(&content, conv)) { fp.Close(); @@ -311,9 +311,9 @@ wxSimpleJSON::Ptr_t wxSimpleJSON::LoadFile(const wxFileName &filename, const wxM return Create(nullptr); } -bool wxSimpleJSON::Save(const wxFileName &filename, const wxMBConv &conv) +bool wxSimpleJSON::Save(const wxString &filename, const wxMBConv &conv) const { - wxFFile fp(filename.GetFullPath(), "wb"); + wxFFile fp(filename, "wb"); if(fp.IsOpened()) { fp.Write(Print(true, conv), conv); fp.Close(); diff --git a/src/wxSimpleJSON.h b/src/wxSimpleJSON.h index 880e827..d2d14fc 100644 --- a/src/wxSimpleJSON.h +++ b/src/wxSimpleJSON.h @@ -102,7 +102,7 @@ class JSON_API_EXPORT wxSimpleJSON * @note Check returned the object by calling IsNull() or IsOk(). * @return A wxSimpleJSON object. */ - static wxSimpleJSON::Ptr_t LoadFile(const wxFileName &filename, + static wxSimpleJSON::Ptr_t LoadFile(const wxString &filename, const wxMBConv &conv = wxConvUTF8); /** @@ -111,7 +111,7 @@ class JSON_API_EXPORT wxSimpleJSON * @param conv The (optional) encoding to save the file with. * @return @c true if the file save succeeded; @c false otherwise. */ - bool Save(const wxFileName &filename, const wxMBConv &conv = wxConvUTF8); + bool Save(const wxString &filename, const wxMBConv &conv = wxConvUTF8) const; /** * @brief Is this object/node null? From 9f6145778c25dabc7aebffa931a0c9bd34b32c04 Mon Sep 17 00:00:00 2001 From: Blake-Madden <66873089+Blake-Madden@users.noreply.github.com> Date: Tue, 9 Sep 2025 18:09:16 -0400 Subject: [PATCH 04/12] Use named enum --- src/wxSimpleJSON.cpp | 4 ++-- src/wxSimpleJSON.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wxSimpleJSON.cpp b/src/wxSimpleJSON.cpp index 31bd114..2a6c690 100644 --- a/src/wxSimpleJSON.cpp +++ b/src/wxSimpleJSON.cpp @@ -96,7 +96,7 @@ wxSimpleJSON &wxSimpleJSON::AddNull(const wxString &name) wxSimpleJSON &wxSimpleJSON::ArrayAdd(const wxArrayString &arr, const wxMBConv &conv) { - wxSimpleJSON::Ptr_t parr = Create(wxSimpleJSON::IS_ARRAY); + wxSimpleJSON::Ptr_t parr = Create(wxSimpleJSON::JSONType::IS_ARRAY); for(size_t i = 0; i < arr.size(); ++i) { parr->ArrayAdd(arr.Item(i), conv); } @@ -107,7 +107,7 @@ wxSimpleJSON &wxSimpleJSON::Add(const wxString &name, const wxArrayString &arr, { DeleteProperty(name); - wxSimpleJSON::Ptr_t parr = Create(wxSimpleJSON::IS_ARRAY); + wxSimpleJSON::Ptr_t parr = Create(wxSimpleJSON::JSONType::IS_ARRAY); for(size_t i = 0; i < arr.size(); ++i) { parr->ArrayAdd(arr.Item(i), conv); } diff --git a/src/wxSimpleJSON.h b/src/wxSimpleJSON.h index d2d14fc..c6620e4 100644 --- a/src/wxSimpleJSON.h +++ b/src/wxSimpleJSON.h @@ -31,7 +31,7 @@ class JSON_API_EXPORT wxSimpleJSON public: /// @brief The data values that JSON supports. - enum JSONType + enum class JSONType { /// @brief Value is invalid. IS_INVALID = 0, From fc6fd08b939442a3dd1bd1d9af610c0b66b65eed Mon Sep 17 00:00:00 2001 From: Blake-Madden <66873089+Blake-Madden@users.noreply.github.com> Date: Tue, 9 Sep 2025 18:12:17 -0400 Subject: [PATCH 05/12] Make Add that takes a wxSimpleJSON::Ptr_t private This should not be public because this is actually copying what the node is pointing to, not just its content. It is easy to cause a circular reference if you don't know what you are doing and clients should no need to call a low-level function like this anyway. --- src/wxSimpleJSON.h | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/wxSimpleJSON.h b/src/wxSimpleJSON.h index c6620e4..35963b1 100644 --- a/src/wxSimpleJSON.h +++ b/src/wxSimpleJSON.h @@ -73,6 +73,16 @@ class JSON_API_EXPORT wxSimpleJSON void SetLastError(const wxString& error) { m_lastError = error; } + /** + * @brief Adds a property to the node with another node. + * @param name The name of the node to add. + * @param obj The other node (along with its properties and values) to add. + * @note If a property with the same name already exists, it will be + * replaced with this one. + * @return A self reference to the node. + */ + wxSimpleJSON& Add(const wxString& name, wxSimpleJSON::Ptr_t obj); + public: // Custom object generators /** @@ -151,15 +161,6 @@ class JSON_API_EXPORT wxSimpleJSON wxSimpleJSON::Ptr_t Item(size_t index) const; // Object manipulation - /** - * @brief Adds a property to the node with another node's content. - * @param name The name of the node to add. - * @param obj The other node (along with its properties and values) to add. - * @note If a property with the same name already exists, it will be - * replaced with this one. - * @return A self reference to the node. - */ - wxSimpleJSON &Add(const wxString &name, wxSimpleJSON::Ptr_t obj); /** * @brief Adds a property to the node with a string value. * @param name The name of the node to add. From d32608c99a6d2516018ee093c95bd78294cf82de Mon Sep 17 00:00:00 2001 From: Blake-Madden <66873089+Blake-Madden@users.noreply.github.com> Date: Tue, 9 Sep 2025 18:13:48 -0400 Subject: [PATCH 06/12] Array returning functions now perform value type validation If you request an array from strings, but the property contains numbers, then the returned vector will now be empty. --- src/wxSimpleJSON.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/wxSimpleJSON.cpp b/src/wxSimpleJSON.cpp index 2a6c690..f0d468f 100644 --- a/src/wxSimpleJSON.cpp +++ b/src/wxSimpleJSON.cpp @@ -144,8 +144,11 @@ wxArrayString wxSimpleJSON::AsArrayString(const wxMBConv &conv) const wxArrayString arr; wxSimpleJSON::Ptr_t parr = Create(m_d); for(size_t i = 0; i < parr->ArraySize(); ++i) { + if (parr->Item(i)->IsValueString()) + { arr.Add(parr->Item(i)->AsString(wxEmptyString, conv)); } + } return arr; } @@ -158,8 +161,11 @@ std::vector wxSimpleJSON::AsStrings(const wxMBConv& conv) const std::vector arr; wxSimpleJSON::Ptr_t parr = Create(m_d); for (size_t i = 0; i < parr->ArraySize(); ++i) { + if (parr->Item(i)->IsValueString()) + { arr.emplace_back(parr->Item(i)->AsString(wxEmptyString, conv)); } + } return arr; } @@ -172,8 +178,11 @@ std::vector wxSimpleJSON::AsNodes() const std::vector arr; wxSimpleJSON::Ptr_t parr = Create(m_d); for(size_t i = 0; i < parr->ArraySize(); ++i) { + if (parr->Item(i)->IsValueObject()) + { arr.push_back(parr->Item(i)); } + } return arr; } @@ -194,8 +203,11 @@ std::vector wxSimpleJSON::AsDoubles(double defaultValue) const std::vector arr; wxSimpleJSON::Ptr_t parr = Create(m_d); for (size_t i = 0; i < parr->ArraySize(); ++i) { + if (parr->Item(i)->IsValueNumber()) + { arr.emplace_back(parr->Item(i)->AsDouble(defaultValue)); } + } return arr; } From 3210322424afaefc6b6a1138e58f73a677fae7aa Mon Sep 17 00:00:00 2001 From: Blake-Madden <66873089+Blake-Madden@users.noreply.github.com> Date: Tue, 9 Sep 2025 18:14:31 -0400 Subject: [PATCH 07/12] Fix DeleteProperty to return false if index value was out of range --- src/wxSimpleJSON.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/wxSimpleJSON.cpp b/src/wxSimpleJSON.cpp index f0d468f..fef101d 100644 --- a/src/wxSimpleJSON.cpp +++ b/src/wxSimpleJSON.cpp @@ -146,8 +146,8 @@ wxArrayString wxSimpleJSON::AsArrayString(const wxMBConv &conv) const for(size_t i = 0; i < parr->ArraySize(); ++i) { if (parr->Item(i)->IsValueString()) { - arr.Add(parr->Item(i)->AsString(wxEmptyString, conv)); - } + arr.Add(parr->Item(i)->AsString(wxEmptyString, conv)); + } } return arr; } @@ -163,8 +163,8 @@ std::vector wxSimpleJSON::AsStrings(const wxMBConv& conv) const for (size_t i = 0; i < parr->ArraySize(); ++i) { if (parr->Item(i)->IsValueString()) { - arr.emplace_back(parr->Item(i)->AsString(wxEmptyString, conv)); - } + arr.emplace_back(parr->Item(i)->AsString(wxEmptyString, conv)); + } } return arr; } @@ -180,8 +180,8 @@ std::vector wxSimpleJSON::AsNodes() const for(size_t i = 0; i < parr->ArraySize(); ++i) { if (parr->Item(i)->IsValueObject()) { - arr.push_back(parr->Item(i)); - } + arr.push_back(parr->Item(i)); + } } return arr; } @@ -205,8 +205,8 @@ std::vector wxSimpleJSON::AsDoubles(double defaultValue) const for (size_t i = 0; i < parr->ArraySize(); ++i) { if (parr->Item(i)->IsValueNumber()) { - arr.emplace_back(parr->Item(i)->AsDouble(defaultValue)); - } + arr.emplace_back(parr->Item(i)->AsDouble(defaultValue)); + } } return arr; } @@ -349,9 +349,11 @@ bool wxSimpleJSON::DeleteProperty(const wxString &name) bool wxSimpleJSON::DeleteProperty(int idx) { - if(!m_d || (m_d->type != cJSON_Array)) { + if(!m_d || (m_d->type != cJSON_Array) || + idx < 0 || idx >= cJSON_GetArraySize(m_d)) { return false; } + cJSON_DeleteItemFromArray(m_d, idx); return true; } From 5e73beaf5a79903e6704b2a2ba5d6abeb6f55de2 Mon Sep 17 00:00:00 2001 From: Blake-Madden <66873089+Blake-Madden@users.noreply.github.com> Date: Tue, 9 Sep 2025 18:16:56 -0400 Subject: [PATCH 08/12] Add unit tests #2 --- tests/CMakeLists.txt | 52 +++++ tests/testmainc.cpp | 456 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 508 insertions(+) create mode 100644 tests/CMakeLists.txt create mode 100644 tests/testmainc.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..152047b --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,52 @@ +############################################################################# +# Name: CMakeLists.txt +# Purpose: Unit test runner for wxSimpleJSON +# Author: Blake Madden +# Created: 2025-09-03 +# Copyright: (c) 2025 Blake Madden +# License: 3-Clause BSD license +############################################################################# + +cmake_minimum_required(VERSION 3.25) +set (CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +project(simplejsontest) + +set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin) + +find_package(Git) +if(NOT Git_FOUND) + # Use MSVC's built-in git, if possible + if (WIN32 AND EXISTS "C:/Program Files/Microsoft Visual Studio/2022/Community/Common7/IDE/CommonExtensions/Microsoft/TeamFoundation/Team Explorer/Git/mingw64/bin/git.exe") + set(GIT_EXECUTABLE "C:/Program Files/Microsoft Visual Studio/2022/Community/Common7/IDE/CommonExtensions/Microsoft/TeamFoundation/Team Explorer/Git/mingw64/bin/git.exe") + else() + message(FATAL_ERROR "Git not found. Unable to fetch needed libraries.") + endif() +endif() + +include(FetchContent) +set(wxBUILD_SHARED OFF) +message(STATUS "Fetching wxWidgets...") +FetchContent_Declare( + wxWidgets + GIT_REPOSITORY https://github.com/wxWidgets/wxWidgets.git + GIT_TAG v3.3.1 # latest dev branch +) +message(STATUS "Fetching Catch2...") +FetchContent_Declare( + Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG v3.10.0 +) +FetchContent_MakeAvailable(wxWidgets Catch2) + +add_executable(${CMAKE_PROJECT_NAME} + ${PROJECT_SOURCE_DIR}/../src/cJSON/cJSON.c + ${PROJECT_SOURCE_DIR}/../src/wxSimpleJSON.cpp + ${PROJECT_SOURCE_DIR}/testmainc.cpp) + +target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE Catch2::Catch2WithMain wx::core wx::base) +include(CTest) +include(Catch) +catch_discover_tests(${CMAKE_PROJECT_NAME}) \ No newline at end of file diff --git a/tests/testmainc.cpp b/tests/testmainc.cpp new file mode 100644 index 0000000..a9b29fe --- /dev/null +++ b/tests/testmainc.cpp @@ -0,0 +1,456 @@ +#include +#include +#include "../src/wxSimpleJSON.h" + +//----------------------------------------------------- +TEST_CASE("String", "[types]") +{ + const auto json = wxSimpleJSON::Create(LR"( +{ +"user-name": "Blake M." +})"); + REQUIRE(json->IsOk()); + auto userName = json->GetProperty(L"user-name"); + REQUIRE(userName->IsOk()); + CHECK_FALSE(userName->IsValueArray()); + CHECK_FALSE(userName->IsValueBoolean()); + CHECK_FALSE(userName->IsValueNull()); + CHECK_FALSE(userName->IsValueNumber()); + CHECK_FALSE(userName->IsValueObject()); + CHECK(userName->IsValueString()); + + CHECK(userName->AsString() == L"Blake M."); +} + +//----------------------------------------------------- +TEST_CASE("Integer", "[types]") +{ + const auto json = wxSimpleJSON::Create(LR"( +{ +"user-name": "Blake M.", +"user-id": 84517 +})"); + REQUIRE(json->IsOk()); + auto userId = json->GetProperty(L"user-id"); + REQUIRE(userId->IsOk()); + CHECK_FALSE(userId->IsValueArray()); + CHECK_FALSE(userId->IsValueBoolean()); + CHECK_FALSE(userId->IsValueNull()); + CHECK(userId->IsValueNumber()); + CHECK_FALSE(userId->IsValueObject()); + CHECK_FALSE(userId->IsValueString()); + + CHECK(userId->AsDouble() == 84517); +} + +//----------------------------------------------------- +TEST_CASE("Double", "[types]") +{ + const auto json = wxSimpleJSON::Create(LR"( +{ +"user-name": "Blake M.", +"user-id": 84517, +"salary": 2200000.97 +})"); + REQUIRE(json->IsOk()); + auto salary = json->GetProperty(L"salary"); + REQUIRE(salary->IsOk()); + CHECK_FALSE(salary->IsValueArray()); + CHECK_FALSE(salary->IsValueBoolean()); + CHECK_FALSE(salary->IsValueNull()); + CHECK(salary->IsValueNumber()); + CHECK_FALSE(salary->IsValueObject()); + CHECK_FALSE(salary->IsValueString()); + + CHECK_THAT(salary->AsDouble(), Catch::Matchers::WithinAbs(2200000.97, 2e-2)); +} + +//----------------------------------------------------- +TEST_CASE("Boolean", "[types]") +{ + const auto json = wxSimpleJSON::Create(LR"( +{ +"user-name": "Blake M.", +"user-id": 84517, +"salary": 2200000.97, +"active" : true +})"); + REQUIRE(json->IsOk()); + auto active = json->GetProperty(L"active"); + REQUIRE(active->IsOk()); + CHECK_FALSE(active->IsValueArray()); + CHECK(active->IsValueBoolean()); + CHECK_FALSE(active->IsValueNull()); + CHECK_FALSE(active->IsValueNumber()); + CHECK_FALSE(active->IsValueObject()); + CHECK_FALSE(active->IsValueString()); + + CHECK(active->AsBool()); +} + +//----------------------------------------------------- +TEST_CASE("Missing Property", "[types]") +{ + const auto json = wxSimpleJSON::Create(LR"( +{ +"user-name": "Blake M.", +"user-id": 84517, +"salary": 2200000.97, +"active" : true +})"); + REQUIRE(json->IsOk()); + // there is no "location" property + auto location = json->GetProperty(L"location"); + CHECK_FALSE(location->IsOk()); + CHECK(location->IsNull()); + CHECK_FALSE(location->IsValueArray()); + CHECK_FALSE(location->IsValueNumber()); + CHECK_FALSE(location->IsValueBoolean()); + CHECK_FALSE(location->IsValueNull()); + CHECK_FALSE(location->IsValueNumber()); + CHECK_FALSE(location->IsValueObject()); + CHECK_FALSE(location->IsValueString()); + // safely return empty/default content + CHECK(location->AsArrayString().empty()); + CHECK(location->AsStrings().empty()); + CHECK(location->AsDoubles().empty()); + CHECK(location->AsBools().empty()); + CHECK(location->AsString().empty()); + CHECK(location->AsStrings().empty()); + CHECK(location->AsDouble(-1) == -1); + CHECK_FALSE(location->AsBool(false)); +} + +//----------------------------------------------------- +TEST_CASE("Empty Array", "[types]") +{ + const auto json = wxSimpleJSON::Create(LR"( +{ +"datasets": [] +})"); + REQUIRE(json->IsOk()); + auto datasets = json->GetProperty(L"datasets"); + REQUIRE(datasets->IsOk()); + CHECK(datasets->IsValueArray()); + CHECK_FALSE(datasets->IsValueBoolean()); + CHECK_FALSE(datasets->IsValueNull()); + CHECK_FALSE(datasets->IsValueNumber()); + CHECK_FALSE(datasets->IsValueObject()); + CHECK_FALSE(datasets->IsValueString()); + + CHECK(datasets->AsStrings().empty()); +} + +//----------------------------------------------------- +TEST_CASE("String Array", "[types]") +{ + const auto json = wxSimpleJSON::Create(LR"( +{ +"datasets": [ "Head Count", "Enrollment" ] +})"); + REQUIRE(json->IsOk()); + auto datasets = json->GetProperty(L"datasets"); + REQUIRE(datasets->IsOk()); + CHECK(datasets->IsValueArray()); + CHECK_FALSE(datasets->IsValueBoolean()); + CHECK_FALSE(datasets->IsValueNull()); + CHECK_FALSE(datasets->IsValueNumber()); + CHECK_FALSE(datasets->IsValueObject()); + CHECK_FALSE(datasets->IsValueString()); + + REQUIRE(datasets->AsStrings().size() == 2); + CHECK(datasets->AsStrings()[0] == L"Head Count"); + CHECK(datasets->AsStrings()[1] == L"Enrollment"); +} + +//----------------------------------------------------- +TEST_CASE("Boolean Array", "[types]") +{ + const auto json = wxSimpleJSON::Create(LR"( +{ +"active": [ false, true, false ] +})"); + REQUIRE(json->IsOk()); + auto actives = json->GetProperty(L"active"); + REQUIRE(actives->IsOk()); + CHECK(actives->IsValueArray()); + CHECK_FALSE(actives->IsValueBoolean()); + CHECK_FALSE(actives->IsValueNull()); + CHECK_FALSE(actives->IsValueNumber()); + CHECK_FALSE(actives->IsValueObject()); + CHECK_FALSE(actives->IsValueString()); + + REQUIRE(actives->AsBools().size() == 3); + CHECK_FALSE(actives->AsBools()[0]); + CHECK(actives->AsBools()[1]); + CHECK_FALSE(actives->AsBools()[2]); +} + +//----------------------------------------------------- +TEST_CASE("Double Array", "[types]") +{ + const auto json = wxSimpleJSON::Create(LR"( +{ +"modes": [ 3.759, 189.842957, 0, 8 ] +})"); + REQUIRE(json->IsOk()); + auto modes = json->GetProperty(L"modes"); + REQUIRE(modes->IsOk()); + CHECK(modes->IsValueArray()); + CHECK_FALSE(modes->IsValueBoolean()); + CHECK_FALSE(modes->IsValueNull()); + CHECK_FALSE(modes->IsValueNumber()); + CHECK_FALSE(modes->IsValueObject()); + CHECK_FALSE(modes->IsValueString()); + + REQUIRE(modes->AsDoubles().size() == 4); + // ensure full precision was read + CHECK_THAT(modes->AsDoubles()[0], Catch::Matchers::WithinAbs(3.759, 2e-3)); + CHECK_THAT(modes->AsDoubles()[1], Catch::Matchers::WithinAbs(189.842957, 2e-6)); + // simple integral values + CHECK(modes->AsDoubles()[2] == 0); + CHECK(modes->AsDoubles()[3] == 8); +} + +//----------------------------------------------------- +TEST_CASE("Node Array", "[types]") + { + const auto json = wxSimpleJSON::Create(LR"( +{ +"datasets": [ + { + "name": "Head Count", + "file-path": "Head Count.txt", + "year": 2025 + }, + { + "name": "Enrollment", + "file-path": "Enrollment.txt", + "year": 1987 + } +] +})"); + REQUIRE(json->IsOk()); + auto datasets = json->GetProperty(L"datasets"); + REQUIRE(datasets->IsOk()); + CHECK(datasets->IsValueArray()); + CHECK_FALSE(datasets->IsValueBoolean()); + CHECK_FALSE(datasets->IsValueNull()); + CHECK_FALSE(datasets->IsValueNumber()); + CHECK_FALSE(datasets->IsValueObject()); + CHECK_FALSE(datasets->IsValueString()); + + // non-sensical conversions should fail, returning to default value + CHECK(datasets->AsDouble() == -1); + CHECK(datasets->AsArrayString().empty()); + CHECK(datasets->AsString().empty()); + CHECK(datasets->AsDoubles().empty()); + CHECK_FALSE(datasets->AsBool()); + + auto blah = datasets->AsDoubles(); + + auto nodes = datasets->AsNodes(); + REQUIRE(nodes.size() == 2); + auto currentDataset = nodes[0]; + REQUIRE(currentDataset->IsOk()); + CHECK(currentDataset->HasProperty(L"name")); + CHECK(currentDataset->GetProperty(L"name")->IsOk()); + CHECK(currentDataset->GetProperty(L"name")->AsString() == L"Head Count"); + + CHECK(currentDataset->HasProperty(L"file-path")); + CHECK(currentDataset->GetProperty(L"file-path")->IsOk()); + CHECK(currentDataset->GetProperty(L"file-path")->AsString() == L"Head Count.txt"); + + CHECK(currentDataset->HasProperty(L"year")); + CHECK(currentDataset->GetProperty(L"year")->IsOk()); + CHECK(currentDataset->GetProperty(L"year")->AsDouble() == 2025); + + // next node + currentDataset = nodes[1]; + REQUIRE(currentDataset->IsOk()); + CHECK(currentDataset->HasProperty(L"name")); + CHECK(currentDataset->GetProperty(L"name")->IsOk()); + CHECK(currentDataset->GetProperty(L"name")->AsString() == L"Enrollment"); + + CHECK(currentDataset->HasProperty(L"file-path")); + CHECK(currentDataset->GetProperty(L"file-path")->IsOk()); + CHECK(currentDataset->GetProperty(L"file-path")->AsString() == L"Enrollment.txt"); + + CHECK(currentDataset->HasProperty(L"year")); + CHECK(currentDataset->GetProperty(L"year")->IsOk()); + CHECK(currentDataset->GetProperty(L"year")->AsDouble() == 1987); + + // everything should have been parsed OK, so shouldn't have any error log + CHECK(json->GetLastError().empty()); + } + +//----------------------------------------------------- +TEST_CASE("Load & Save", "[file]") +{ + const auto json = wxSimpleJSON::Create(LR"( +{ +"datasets": [ + { + "name": "Head Count", + "file-path": "Head Count.txt", + "year": 2025 + }, + { + "name": "Enrollment", + "file-path": "Enrollment.txt", + "year": 1987 + } +] +})"); + REQUIRE(json->IsOk()); + REQUIRE(json->Save(wxFileName::GetTempDir() + L"/json.tmp")); + + const auto jsonFromFile = wxSimpleJSON::LoadFile(wxFileName::GetTempDir() + L"/json.tmp"); + REQUIRE(jsonFromFile->IsOk()); + auto datasets = jsonFromFile->GetProperty(L"datasets"); + REQUIRE(datasets->IsOk()); + CHECK(datasets->IsValueArray()); + CHECK_FALSE(datasets->IsValueBoolean()); + CHECK_FALSE(datasets->IsValueNull()); + CHECK_FALSE(datasets->IsValueNumber()); + CHECK_FALSE(datasets->IsValueObject()); + CHECK_FALSE(datasets->IsValueString()); + + // non-sensical conversions should fail, returning to default value + CHECK(datasets->AsDouble() == -1); + CHECK(datasets->AsArrayString().empty()); + CHECK(datasets->AsString().empty()); + CHECK(datasets->AsDoubles().empty()); + CHECK_FALSE(datasets->AsBool()); + + auto blah = datasets->AsDoubles(); + + auto nodes = datasets->AsNodes(); + REQUIRE(nodes.size() == 2); + auto currentDataset = nodes[0]; + REQUIRE(currentDataset->IsOk()); + CHECK(currentDataset->HasProperty(L"name")); + CHECK(currentDataset->GetProperty(L"name")->IsOk()); + CHECK(currentDataset->GetProperty(L"name")->AsString() == L"Head Count"); + + CHECK(currentDataset->HasProperty(L"file-path")); + CHECK(currentDataset->GetProperty(L"file-path")->IsOk()); + CHECK(currentDataset->GetProperty(L"file-path")->AsString() == L"Head Count.txt"); + + CHECK(currentDataset->HasProperty(L"year")); + CHECK(currentDataset->GetProperty(L"year")->IsOk()); + CHECK(currentDataset->GetProperty(L"year")->AsDouble() == 2025); + + // next node + currentDataset = nodes[1]; + REQUIRE(currentDataset->IsOk()); + CHECK(currentDataset->HasProperty(L"name")); + CHECK(currentDataset->GetProperty(L"name")->IsOk()); + CHECK(currentDataset->GetProperty(L"name")->AsString() == L"Enrollment"); + + CHECK(currentDataset->HasProperty(L"file-path")); + CHECK(currentDataset->GetProperty(L"file-path")->IsOk()); + CHECK(currentDataset->GetProperty(L"file-path")->AsString() == L"Enrollment.txt"); + + CHECK(currentDataset->HasProperty(L"year")); + CHECK(currentDataset->GetProperty(L"year")->IsOk()); + CHECK(currentDataset->GetProperty(L"year")->AsDouble() == 1987); + + // everything should have been parsed OK, so shouldn't have any error log + CHECK(json->GetLastError().empty()); +} + +//----------------------------------------------------- +TEST_CASE("Bad Load", "[file]") +{ + if (!wxFileName::FileExists(wxFileName::GetTempDir() + L"/json_this_likely_is_missing.tmp")) + { + const auto jsonFromFile = wxSimpleJSON::LoadFile(wxFileName::GetTempDir() + L"/json_this_likely_is_missing.tmp"); + REQUIRE_FALSE(jsonFromFile->IsOk()); + REQUIRE(jsonFromFile->IsNull()); + } +} + +//----------------------------------------------------- +TEST_CASE("Garbage", "[fuzzing]") +{ + // a missing quote, should fail gracefully with an error log + const auto json = wxSimpleJSON::Create(LR"( +{ +"datasets": [ "Head Count", Enrollment" ] +})"); + REQUIRE_FALSE(json->IsOk()); + CHECK_FALSE(json->GetLastError().empty()); +} + +//----------------------------------------------------- +TEST_CASE("Delete", "[add/delete]") +{ + const auto json = wxSimpleJSON::Create(LR"( +{ +"user-name": "Blake M.", +"user-id": 84517, +"salary": 2200000.97, +"status" : [ "active", "remote" ] +})"); + REQUIRE(json->IsOk()); + // out of range or non-existent + CHECK_FALSE(json->DeleteProperty(-1)); + CHECK_FALSE(json->DeleteProperty(10)); + CHECK_FALSE(json->DeleteProperty(L"bogus")); + CHECK_FALSE(json->DeleteProperty(L"")); + + CHECK(json->GetProperty(L"status")->IsOk()); + CHECK_FALSE(json->GetProperty(L"status")->DeleteProperty(-10)); // out of range + CHECK_FALSE(json->GetProperty(L"status")->DeleteProperty(2)); // out of range + CHECK(json->GetProperty(L"status")->DeleteProperty(0)); + REQUIRE(json->GetProperty(L"status")->AsStrings().size() == 1); + REQUIRE(json->GetProperty(L"status")->AsStrings()[0] == L"remote"); +} + +//----------------------------------------------------- +TEST_CASE("Add", "[add/delete]") +{ + const auto json = wxSimpleJSON::Create(LR"( +{ +"user-name": "Blake M.", +"user-id": 84517, +"salary": 2200000.97, +"status" : [ "active", "remote" ] +})"); + + auto blah = json->Print(); + + REQUIRE(json->IsOk()); + json->Add(L"user-name", wxString{ L"Stefano" }); + auto userName = json->GetProperty(L"user-name"); + CHECK(userName->IsOk()); + CHECK(userName->AsString() == wxString{ L"Stefano" }); + + // make it a number + json->Add(L"user-name", 105756.0); + userName = json->GetProperty(L"user-name"); + CHECK(userName->IsOk()); + CHECK(userName->AsDouble() == 105756.0); + + // make it a bool + json->Add(L"user-name", true); + userName = json->GetProperty(L"user-name"); + CHECK(userName->IsOk()); + CHECK(userName->AsBool()); + + // make it an array of strings + json->Add(L"user-name", wxArrayString{ L"Blake M.", L"Stefano" }); + userName = json->GetProperty(L"user-name"); + CHECK(userName->IsOk()); + REQUIRE(userName->AsStrings().size() == 2); + CHECK(userName->AsStrings()[0] == L"Blake M."); + CHECK(userName->AsStrings()[1] == L"Stefano"); + + // add an entirely new node + json->Add(L"location", wxString{ L"Ohio" }); + auto location = json->GetProperty(L"location"); + CHECK(location->IsOk()); + CHECK_FALSE(location->IsNull()); + CHECK(location->AsString() == wxString{ L"Ohio" }); +} \ No newline at end of file From 4c159012dea8797174349eb5892589bedb0f5caa Mon Sep 17 00:00:00 2001 From: Blake-Madden <66873089+Blake-Madden@users.noreply.github.com> Date: Tue, 23 Sep 2025 18:35:51 -0400 Subject: [PATCH 09/12] Fix AsNodes() to return everything --- src/wxSimpleJSON.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/wxSimpleJSON.cpp b/src/wxSimpleJSON.cpp index fef101d..574bfcf 100644 --- a/src/wxSimpleJSON.cpp +++ b/src/wxSimpleJSON.cpp @@ -178,10 +178,7 @@ std::vector wxSimpleJSON::AsNodes() const std::vector arr; wxSimpleJSON::Ptr_t parr = Create(m_d); for(size_t i = 0; i < parr->ArraySize(); ++i) { - if (parr->Item(i)->IsValueObject()) - { arr.push_back(parr->Item(i)); - } } return arr; } From 6f2fb669901c4ed4c580a223e0ef16ef6ede1af9 Mon Sep 17 00:00:00 2001 From: Blake-Madden <66873089+Blake-Madden@users.noreply.github.com> Date: Thu, 25 Sep 2025 18:59:32 -0400 Subject: [PATCH 10/12] Use cleaner way to find VS's built-in git --- tests/CMakeLists.txt | 9 ++------- tests/FindGit.cmake | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 7 deletions(-) create mode 100644 tests/FindGit.cmake diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 152047b..f0c8f4b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -15,14 +15,9 @@ project(simplejsontest) set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin) -find_package(Git) +include(FindGit.cmake) if(NOT Git_FOUND) - # Use MSVC's built-in git, if possible - if (WIN32 AND EXISTS "C:/Program Files/Microsoft Visual Studio/2022/Community/Common7/IDE/CommonExtensions/Microsoft/TeamFoundation/Team Explorer/Git/mingw64/bin/git.exe") - set(GIT_EXECUTABLE "C:/Program Files/Microsoft Visual Studio/2022/Community/Common7/IDE/CommonExtensions/Microsoft/TeamFoundation/Team Explorer/Git/mingw64/bin/git.exe") - else() - message(FATAL_ERROR "Git not found. Unable to fetch needed libraries.") - endif() + message(FATAL_ERROR "Git not found. Unable to fetch needed libraries.") endif() include(FetchContent) diff --git a/tests/FindGit.cmake b/tests/FindGit.cmake new file mode 100644 index 0000000..f8617a1 --- /dev/null +++ b/tests/FindGit.cmake @@ -0,0 +1,39 @@ +# --- Look up Visual Studio's bundled Git via vswhere (VS 2017+) + +# Build a list of candidate vswhere locations without touching ProgramFiles(x86) +set(_VSWHERE_HINTS) + +if(DEFINED ENV{ProgramW6432}) + list(APPEND _VSWHERE_HINTS "$ENV{ProgramW6432}/Microsoft Visual Studio/Installer") +endif() + +if(DEFINED ENV{ProgramFiles}) + list(APPEND _VSWHERE_HINTS "$ENV{ProgramFiles}/Microsoft Visual Studio/Installer") +endif() + +# Also allow PATH/CMAKE_PROGRAM_PATH to help if vswhere is installed elsewhere +find_program(VSWHERE_EXECUTABLE NAMES vswhere + HINTS ${_VSWHERE_HINTS} + NO_CACHE +) + +# Ask VSWhere for the Git bundled with Visual Studio +if(VSWHERE_EXECUTABLE AND NOT DEFINED GIT_EXECUTABLE) + execute_process( + COMMAND "${VSWHERE_EXECUTABLE}" + -latest + -products * + -requires Microsoft.VisualStudio.Component.Git + -find **\\Git\\cmd\\git.exe + OUTPUT_VARIABLE VS_GIT_PATH + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + ) + if(VS_GIT_PATH AND EXISTS "${VS_GIT_PATH}") + set(GIT_EXECUTABLE "${VS_GIT_PATH}" CACHE FILEPATH "Git from Visual Studio") + mark_as_advanced(GIT_EXECUTABLE) + endif() +endif() + +# Fall back to normal PATH search (will use GIT_EXECUTABLE if we set it above) +find_package(Git QUIET) From b57512beb8bb55c021678168cfc77075002b2ec9 Mon Sep 17 00:00:00 2001 From: Blake-Madden <66873089+Blake-Madden@users.noreply.github.com> Date: Mon, 29 Sep 2025 07:44:57 -0400 Subject: [PATCH 11/12] Add unit test for node array with different types --- tests/testmainc.cpp | 48 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/tests/testmainc.cpp b/tests/testmainc.cpp index a9b29fe..03cc733 100644 --- a/tests/testmainc.cpp +++ b/tests/testmainc.cpp @@ -245,9 +245,7 @@ TEST_CASE("Node Array", "[types]") CHECK(datasets->AsArrayString().empty()); CHECK(datasets->AsString().empty()); CHECK(datasets->AsDoubles().empty()); - CHECK_FALSE(datasets->AsBool()); - - auto blah = datasets->AsDoubles(); + CHECK_FALSE(datasets->AsBool()); auto nodes = datasets->AsNodes(); REQUIRE(nodes.size() == 2); @@ -284,6 +282,50 @@ TEST_CASE("Node Array", "[types]") CHECK(json->GetLastError().empty()); } +//----------------------------------------------------- +TEST_CASE("Node Array Different Types", "[types]") +{ + const auto json = wxSimpleJSON::Create(LR"( +{ +"years": [ 1972, "1973", 1974 ] +})"); + REQUIRE(json->IsOk()); + auto years = json->GetProperty(L"years"); + REQUIRE(years->IsOk()); + CHECK(years->IsValueArray()); + CHECK_FALSE(years->IsValueBoolean()); + CHECK_FALSE(years->IsValueNull()); + CHECK_FALSE(years->IsValueNumber()); + CHECK_FALSE(years->IsValueObject()); + CHECK_FALSE(years->IsValueString()); + + CHECK(years->AsDouble() == -1); + CHECK(years->AsArrayString().size() == 1); + CHECK(years->AsString().empty()); + CHECK(years->AsDoubles().size() == 2); + CHECK_FALSE(years->AsBool()); + + auto nodes = years->AsNodes(); + REQUIRE(nodes.size() == 3); + auto currentYear = nodes[0]; + REQUIRE(currentYear->IsOk()); + CHECK(currentYear->IsValueNumber()); + CHECK(currentYear->AsDouble() == 1972); + + currentYear = nodes[1]; + REQUIRE(currentYear->IsOk()); + CHECK(currentYear->IsValueString()); + CHECK(currentYear->AsString() == L"1973"); + + currentYear = nodes[2]; + REQUIRE(currentYear->IsOk()); + CHECK(currentYear->IsValueNumber()); + CHECK(currentYear->AsDouble() == 1974); + + // everything should have been parsed OK, so shouldn't have any error log + CHECK(json->GetLastError().empty()); +} + //----------------------------------------------------- TEST_CASE("Load & Save", "[file]") { From 1e3139f5277e6519a4afd36ff487d2ab08ed03bb Mon Sep 17 00:00:00 2001 From: Blake-Madden <66873089+Blake-Madden@users.noreply.github.com> Date: Sat, 4 Oct 2025 09:03:34 -0400 Subject: [PATCH 12/12] Use utf8_str instead of mb_str(wxConvUTF8) Does the same thing, but is much cleaner and easier to read. --- src/wxSimpleJSON.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/wxSimpleJSON.cpp b/src/wxSimpleJSON.cpp index 574bfcf..59b384a 100644 --- a/src/wxSimpleJSON.cpp +++ b/src/wxSimpleJSON.cpp @@ -66,7 +66,7 @@ wxSimpleJSON &wxSimpleJSON::Add(const wxString &name, wxSimpleJSON::Ptr_t obj) { DeleteProperty(name); - cJSON_AddItemToObject(m_d, name.mb_str(wxConvUTF8).data(), obj->m_d); + cJSON_AddItemToObject(m_d, name.utf8_str().data(), obj->m_d); return *this; } @@ -74,7 +74,7 @@ wxSimpleJSON &wxSimpleJSON::Add(const wxString &name, const wxString &value, con { DeleteProperty(name); - cJSON_AddStringToObject(m_d, name.mb_str(wxConvUTF8).data(), value.mb_str(conv).data()); + cJSON_AddStringToObject(m_d, name.utf8_str().data(), value.mb_str(conv).data()); return *this; } @@ -82,7 +82,7 @@ wxSimpleJSON &wxSimpleJSON::Add(const wxString &name, double value) { DeleteProperty(name); - cJSON_AddNumberToObject(m_d, name.mb_str(wxConvUTF8).data(), value); + cJSON_AddNumberToObject(m_d, name.utf8_str().data(), value); return *this; } @@ -90,7 +90,7 @@ wxSimpleJSON &wxSimpleJSON::AddNull(const wxString &name) { DeleteProperty(name); - cJSON_AddNullToObject(m_d, name.mb_str(wxConvUTF8).data()); + cJSON_AddNullToObject(m_d, name.utf8_str().data()); return *this; } @@ -213,7 +213,7 @@ wxSimpleJSON::Ptr_t wxSimpleJSON::GetProperty(const wxString &name) const if(!m_d || (m_d->type != cJSON_Object)) { return Create(nullptr); } - return Create(cJSON_GetObjectItem(m_d, name.mb_str(wxConvUTF8).data())); + return Create(cJSON_GetObjectItem(m_d, name.utf8_str().data())); } wxSimpleJSON &wxSimpleJSON::ArrayAdd(bool value) @@ -238,7 +238,7 @@ wxSimpleJSON &wxSimpleJSON::Add(const wxString &name, bool value) if(!m_d || (m_d->type != cJSON_Object)) { return *this; } - cJSON_AddBoolToObject(m_d, name.mb_str(wxConvUTF8).data(), value); + cJSON_AddBoolToObject(m_d, name.utf8_str().data(), value); return *this; } @@ -336,11 +336,11 @@ bool wxSimpleJSON::DeleteProperty(const wxString &name) if(!m_d || (m_d->type != cJSON_Object)) { return false; } - cJSON *p = cJSON_GetObjectItem(m_d, name.mb_str(wxConvUTF8).data()); + cJSON *p = cJSON_GetObjectItem(m_d, name.utf8_str().data()); if(!p) { return false; } - cJSON_DeleteItemFromObject(m_d, name.mb_str(wxConvUTF8).data()); + cJSON_DeleteItemFromObject(m_d, name.utf8_str().data()); return true; } @@ -360,7 +360,7 @@ bool wxSimpleJSON::HasProperty(const wxString& name) if(!m_d || (m_d->type != cJSON_Object)) { return false; } - cJSON *p = cJSON_GetObjectItem(m_d, name.mb_str(wxConvUTF8).data()); + cJSON *p = cJSON_GetObjectItem(m_d, name.utf8_str().data()); if(!p) { return false; }