From dc52aafe26114ffbf677af3326170217f4e68e4c Mon Sep 17 00:00:00 2001 From: Roland Rabien Date: Mon, 18 Nov 2024 08:44:10 -0800 Subject: [PATCH 1/6] Add some json helpers --- modules/juce_core/containers/juce_Variant.cpp | 17 +++++++ modules/juce_core/containers/juce_Variant.h | 2 + modules/juce_core/json/juce_JSONUtils.cpp | 51 +++++++++++++++++++ modules/juce_core/json/juce_JSONUtils.h | 11 ++++ 4 files changed, 81 insertions(+) diff --git a/modules/juce_core/containers/juce_Variant.cpp b/modules/juce_core/containers/juce_Variant.cpp index 0b45553bef..4c3b934436 100644 --- a/modules/juce_core/containers/juce_Variant.cpp +++ b/modules/juce_core/containers/juce_Variant.cpp @@ -708,6 +708,21 @@ bool var::hasProperty (const Identifier& propertyName) const noexcept return false; } +juce::StringArray var::getProperties() const +{ + if (auto* o = getDynamicObject()) + { + juce::StringArray names; + + for (auto itr : o->getProperties()) + names.add (itr.name.toString()); + + return names; + } + + return {}; +} + var::NativeFunction var::getNativeFunction() const { return isMethod() && (value.methodValue != nullptr) ? *value.methodValue : nullptr; @@ -893,6 +908,8 @@ var::NativeFunctionArgs::NativeFunctionArgs (const var& t, const var* args, int { } +//============================================================================== + //============================================================================== #if JUCE_ALLOW_STATIC_NULL_VARIABLES diff --git a/modules/juce_core/containers/juce_Variant.h b/modules/juce_core/containers/juce_Variant.h index 3e70efd3f6..a6bcb97b8d 100644 --- a/modules/juce_core/containers/juce_Variant.h +++ b/modules/juce_core/containers/juce_Variant.h @@ -262,6 +262,8 @@ public: var getProperty (const Identifier& propertyName, const var& defaultReturnValue) const; /** Returns true if this variant is an object and if it has the given property. */ bool hasProperty (const Identifier& propertyName) const noexcept; + /** Returns property names if this variant is an object. */ + juce::StringArray getProperties() const; /** Invokes a named method call with no arguments. */ var call (const Identifier& method) const; diff --git a/modules/juce_core/json/juce_JSONUtils.cpp b/modules/juce_core/json/juce_JSONUtils.cpp index 3652023af6..3d96967f23 100644 --- a/modules/juce_core/json/juce_JSONUtils.cpp +++ b/modules/juce_core/json/juce_JSONUtils.cpp @@ -124,6 +124,57 @@ std::optional JSONUtils::setPointer (const var& v, return {}; } +var JSONUtils::getPointer (const var& v, String pointer, const var& defaultValue) +{ + if (pointer.isEmpty()) + return defaultValue; + + if (! pointer.startsWith ("/")) + { + // This is not a well-formed JSON pointer + jassertfalse; + return {}; + } + + const auto findResult = pointer.indexOfChar (1, '/'); + const auto pos = findResult < 0 ? pointer.length() : findResult; + const String head (pointer.begin() + 1, pointer.begin() + pos); + const String tail (pointer.begin() + pos, pointer.end()); + + const auto unescaped = head.replace ("~1", "/").replace ("~0", "~"); + + if (auto* object = v.getDynamicObject()) + { + if (tail.isEmpty()) + return object->hasProperty (unescaped) ? object->getProperty (unescaped) : defaultValue; + else + return getPointer (object->getProperty (unescaped), tail, defaultValue); + } + else if (auto* array = v.getArray()) + { + const auto index = [&]() -> size_t + { + if (unescaped == "-") + return (size_t) array->size(); + + if (unescaped == "0") + return 0; + + if (! unescaped.startsWith ("0")) + return (size_t) unescaped.getLargeIntValue(); + + return std::numeric_limits::max(); + }(); + + if (tail.isEmpty()) + return isPositiveAndBelow (index, array->size()) ? (*array)[int (index)] : defaultValue; + else + return getPointer ((*array)[(int) index], tail, defaultValue); + } + + return defaultValue; +} + bool JSONUtils::deepEqual (const var& a, const var& b) { const auto compareObjects = [] (const DynamicObject& x, const DynamicObject& y) diff --git a/modules/juce_core/json/juce_JSONUtils.h b/modules/juce_core/json/juce_JSONUtils.h index 5ea77bab21..16c0bdb6b5 100644 --- a/modules/juce_core/json/juce_JSONUtils.h +++ b/modules/juce_core/json/juce_JSONUtils.h @@ -57,6 +57,17 @@ struct JSONUtils */ static std::optional setPointer (const var& v, String pointer, const var& newValue); + /** Given a JSON array/object 'v', a string representing a JSON pointer, + returns the value of the property or array index referenced by the pointer + + If the pointer cannot be followed, due to referencing missing array indices + or fields, then this returns defaultValue. + + For more details, check the JSON Pointer RFC 6901: + https://datatracker.ietf.org/doc/html/rfc6901 + */ + static var getPointer (const var& v, String pointer, const var& defaultValue); + /** Converts the provided key/value pairs into a JSON object. */ static var makeObject (const std::map& source); From f5e77c483d96c3ca8abec1ded7a564d8d514570f Mon Sep 17 00:00:00 2001 From: Roland Rabien Date: Mon, 18 Nov 2024 08:52:50 -0800 Subject: [PATCH 2/6] JSONUtils::getPointer unit tests --- modules/juce_core/json/juce_JSONUtils.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/juce_core/json/juce_JSONUtils.cpp b/modules/juce_core/json/juce_JSONUtils.cpp index 3d96967f23..28dc0b08a4 100644 --- a/modules/juce_core/json/juce_JSONUtils.cpp +++ b/modules/juce_core/json/juce_JSONUtils.cpp @@ -223,6 +223,13 @@ public: , "lfoWaveform": "triangle" , "pitchEnvelope": { "rates": [94,67,95,60], "levels": [50,50,50,50] } })"); + + expect (JSONUtils::getPointer (obj, "/name", {}) == "PIANO 4"); + expect (JSONUtils::getPointer (obj, "/lfoSpeed", {}) == var (30)); + expect (JSONUtils::getPointer (obj, "/pitchEnvelope/rates/1", {}) == var (67)); + expect (JSONUtils::getPointer (obj, "/pitchEnvelope/levels/2", {}) == var (50)); + expect (JSONUtils::getPointer (obj, "/pitchEnvelope/levels/10", {}) == var()); + expectDeepEqual (JSONUtils::setPointer (obj, "", "hello world"), var ("hello world")); expectDeepEqual (JSONUtils::setPointer (obj, "/lfoWaveform/foobar", "str"), std::nullopt); expectDeepEqual (JSONUtils::setPointer (JSON::parse (R"({"foo":0,"bar":1})"), "/foo", 2), JSON::parse (R"({"foo":2,"bar":1})")); From 6547af31102caaf99d9a29a1ff4a1bd29789b455 Mon Sep 17 00:00:00 2001 From: Roland Rabien Date: Mon, 18 Nov 2024 09:27:40 -0800 Subject: [PATCH 3/6] Add iterator to var --- modules/juce_core/containers/juce_Variant.cpp | 67 +++++++++++++++++++ modules/juce_core/containers/juce_Variant.h | 38 +++++++++++ 2 files changed, 105 insertions(+) diff --git a/modules/juce_core/containers/juce_Variant.cpp b/modules/juce_core/containers/juce_Variant.cpp index 4c3b934436..43a9e4df59 100644 --- a/modules/juce_core/containers/juce_Variant.cpp +++ b/modules/juce_core/containers/juce_Variant.cpp @@ -909,6 +909,73 @@ var::NativeFunctionArgs::NativeFunctionArgs (const var& t, const var* args, int } //============================================================================== +VarIterator::VarIterator (const var& v_, bool isEnd) + : v (v_) +{ + if (isEnd) + { + if (v.isArray()) + index = v.getArray()->size(); + else if (auto o = v.getDynamicObject()) + itr = o->getProperties().end(); + } + else + { + if (v.isArray()) + index = 0; + else if (auto o = v.getDynamicObject()) + itr = o->getProperties().begin(); + } +} + +VarIterator& VarIterator::operator++() +{ + if (v.isArray()) + { + index++; + } + else if (v.getDynamicObject()) + { + auto i = (const NamedValueSet::NamedValue*)itr; + i++; + itr = i; + } + return *this; +} + +bool VarIterator::operator== (const VarIterator& other) const +{ + return index == other.index && itr == other.itr; +} + +bool VarIterator::operator!= (const VarIterator& other) const +{ + return ! (*this == other); +} + +VarIterator::NamedValue VarIterator::operator*() const +{ + if (v.isArray()) + { + return { index, (*v.getArray())[index] }; + } + else if (v.getDynamicObject()) + { + auto i = (const NamedValueSet::NamedValue*)itr; + return { i->name.toString(), i->value }; + } + return {}; +} + +VarIterator begin (const var& v) +{ + return VarIterator (v, false); +} + +VarIterator end (const var& v) +{ + return VarIterator (v, true); +} //============================================================================== #if JUCE_ALLOW_STATIC_NULL_VARIABLES diff --git a/modules/juce_core/containers/juce_Variant.h b/modules/juce_core/containers/juce_Variant.h index a6bcb97b8d..cee346dbbb 100644 --- a/modules/juce_core/containers/juce_Variant.h +++ b/modules/juce_core/containers/juce_Variant.h @@ -354,4 +354,42 @@ JUCE_API bool operator== (const var&, const String&); JUCE_API bool operator!= (const var&, const String&); JUCE_API bool operator== (const var&, const char*); JUCE_API bool operator!= (const var&, const char*); + + +//============================================================================== +/** Iterator for a var. + You shouldn't ever need to use this class directly - it's used internally by begin() + and end() to allow range-based-for loops on a var. +*/ +struct VarIterator +{ + struct NamedValue + { + var name; + var value; + }; + + VarIterator (const var&, bool isEnd); + VarIterator& operator++(); + + bool operator== (const VarIterator&) const; + bool operator!= (const VarIterator&) const; + NamedValue operator*() const; + + using difference_type = std::ptrdiff_t; + using value_type = NamedValue; + using reference = NamedValue&; + using pointer = NamedValue*; + using iterator_category = std::forward_iterator_tag; + +private: + const var& v; + int index = 0; + const void* itr = nullptr; +}; + +VarIterator begin (const var&); +VarIterator end (const var&); + + } // namespace juce From be5e22c1c2148c3c26da85e1489b1b8f2154da1c Mon Sep 17 00:00:00 2001 From: Roland Rabien Date: Mon, 18 Nov 2024 09:27:55 -0800 Subject: [PATCH 4/6] Remove tabs --- modules/juce_core/containers/juce_Variant.cpp | 80 +++++++++---------- modules/juce_core/containers/juce_Variant.h | 40 +++++----- modules/juce_core/json/juce_JSONUtils.cpp | 10 +-- 3 files changed, 65 insertions(+), 65 deletions(-) diff --git a/modules/juce_core/containers/juce_Variant.cpp b/modules/juce_core/containers/juce_Variant.cpp index 43a9e4df59..62181c778b 100644 --- a/modules/juce_core/containers/juce_Variant.cpp +++ b/modules/juce_core/containers/juce_Variant.cpp @@ -910,71 +910,71 @@ var::NativeFunctionArgs::NativeFunctionArgs (const var& t, const var* args, int //============================================================================== VarIterator::VarIterator (const var& v_, bool isEnd) - : v (v_) + : v (v_) { - if (isEnd) - { - if (v.isArray()) - index = v.getArray()->size(); - else if (auto o = v.getDynamicObject()) - itr = o->getProperties().end(); - } - else - { - if (v.isArray()) - index = 0; - else if (auto o = v.getDynamicObject()) - itr = o->getProperties().begin(); - } + if (isEnd) + { + if (v.isArray()) + index = v.getArray()->size(); + else if (auto o = v.getDynamicObject()) + itr = o->getProperties().end(); + } + else + { + if (v.isArray()) + index = 0; + else if (auto o = v.getDynamicObject()) + itr = o->getProperties().begin(); + } } VarIterator& VarIterator::operator++() { - if (v.isArray()) - { - index++; - } - else if (v.getDynamicObject()) - { - auto i = (const NamedValueSet::NamedValue*)itr; - i++; - itr = i; - } - return *this; + if (v.isArray()) + { + index++; + } + else if (v.getDynamicObject()) + { + auto i = (const NamedValueSet::NamedValue*)itr; + i++; + itr = i; + } + return *this; } bool VarIterator::operator== (const VarIterator& other) const { - return index == other.index && itr == other.itr; + return index == other.index && itr == other.itr; } bool VarIterator::operator!= (const VarIterator& other) const { - return ! (*this == other); + return ! (*this == other); } VarIterator::NamedValue VarIterator::operator*() const { - if (v.isArray()) - { - return { index, (*v.getArray())[index] }; - } - else if (v.getDynamicObject()) - { - auto i = (const NamedValueSet::NamedValue*)itr; - return { i->name.toString(), i->value }; - } - return {}; + if (v.isArray()) + { + return { index, (*v.getArray())[index] }; + } + else if (v.getDynamicObject()) + { + auto i = (const NamedValueSet::NamedValue*)itr; + return { i->name.toString(), i->value }; + } + return {}; } VarIterator begin (const var& v) { - return VarIterator (v, false); + return VarIterator (v, false); } VarIterator end (const var& v) { - return VarIterator (v, true); + return VarIterator (v, true); } //============================================================================== diff --git a/modules/juce_core/containers/juce_Variant.h b/modules/juce_core/containers/juce_Variant.h index cee346dbbb..be5fa893a6 100644 --- a/modules/juce_core/containers/juce_Variant.h +++ b/modules/juce_core/containers/juce_Variant.h @@ -358,34 +358,34 @@ JUCE_API bool operator!= (const var&, const char*); //============================================================================== /** Iterator for a var. - You shouldn't ever need to use this class directly - it's used internally by begin() - and end() to allow range-based-for loops on a var. + You shouldn't ever need to use this class directly - it's used internally by begin() + and end() to allow range-based-for loops on a var. */ struct VarIterator { - struct NamedValue - { - var name; - var value; - }; + struct NamedValue + { + var name; + var value; + }; - VarIterator (const var&, bool isEnd); - VarIterator& operator++(); + VarIterator (const var&, bool isEnd); + VarIterator& operator++(); - bool operator== (const VarIterator&) const; - bool operator!= (const VarIterator&) const; - NamedValue operator*() const; + bool operator== (const VarIterator&) const; + bool operator!= (const VarIterator&) const; + NamedValue operator*() const; - using difference_type = std::ptrdiff_t; - using value_type = NamedValue; - using reference = NamedValue&; - using pointer = NamedValue*; - using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = NamedValue; + using reference = NamedValue&; + using pointer = NamedValue*; + using iterator_category = std::forward_iterator_tag; private: - const var& v; - int index = 0; - const void* itr = nullptr; + const var& v; + int index = 0; + const void* itr = nullptr; }; VarIterator begin (const var&); diff --git a/modules/juce_core/json/juce_JSONUtils.cpp b/modules/juce_core/json/juce_JSONUtils.cpp index 28dc0b08a4..a17bf947ce 100644 --- a/modules/juce_core/json/juce_JSONUtils.cpp +++ b/modules/juce_core/json/juce_JSONUtils.cpp @@ -224,11 +224,11 @@ public: , "pitchEnvelope": { "rates": [94,67,95,60], "levels": [50,50,50,50] } })"); - expect (JSONUtils::getPointer (obj, "/name", {}) == "PIANO 4"); - expect (JSONUtils::getPointer (obj, "/lfoSpeed", {}) == var (30)); - expect (JSONUtils::getPointer (obj, "/pitchEnvelope/rates/1", {}) == var (67)); - expect (JSONUtils::getPointer (obj, "/pitchEnvelope/levels/2", {}) == var (50)); - expect (JSONUtils::getPointer (obj, "/pitchEnvelope/levels/10", {}) == var()); + expect (JSONUtils::getPointer (obj, "/name", {}) == "PIANO 4"); + expect (JSONUtils::getPointer (obj, "/lfoSpeed", {}) == var (30)); + expect (JSONUtils::getPointer (obj, "/pitchEnvelope/rates/1", {}) == var (67)); + expect (JSONUtils::getPointer (obj, "/pitchEnvelope/levels/2", {}) == var (50)); + expect (JSONUtils::getPointer (obj, "/pitchEnvelope/levels/10", {}) == var()); expectDeepEqual (JSONUtils::setPointer (obj, "", "hello world"), var ("hello world")); expectDeepEqual (JSONUtils::setPointer (obj, "/lfoWaveform/foobar", "str"), std::nullopt); From 09d8ea8864444ef3c81c3200cb228b5320457708 Mon Sep 17 00:00:00 2001 From: Roland Rabien Date: Tue, 19 Nov 2024 09:10:27 -0800 Subject: [PATCH 5/6] Added JSONUtils::updatePointer --- modules/juce_core/json/juce_JSONUtils.cpp | 68 +++++++++++++++++++++++ modules/juce_core/json/juce_JSONUtils.h | 12 ++++ 2 files changed, 80 insertions(+) diff --git a/modules/juce_core/json/juce_JSONUtils.cpp b/modules/juce_core/json/juce_JSONUtils.cpp index a17bf947ce..3f0b56a405 100644 --- a/modules/juce_core/json/juce_JSONUtils.cpp +++ b/modules/juce_core/json/juce_JSONUtils.cpp @@ -124,6 +124,74 @@ std::optional JSONUtils::setPointer (const var& v, return {}; } +bool JSONUtils::updatePointer (var& v, String pointer, const var& newValue) +{ + if (pointer.isEmpty()) + return false; + + if (! pointer.startsWith ("/")) + { + // This is not a well-formed JSON pointer + jassertfalse; + return {}; + } + + const auto findResult = pointer.indexOfChar (1, '/'); + const auto pos = findResult < 0 ? pointer.length() : findResult; + const String head (pointer.begin() + 1, pointer.begin() + pos); + const String tail (pointer.begin() + pos, pointer.end()); + + const auto unescaped = head.replace ("~1", "/").replace ("~0", "~"); + + if (auto* object = v.getDynamicObject()) + { + if (tail.isEmpty()) + { + object->setProperty (unescaped, newValue); + return true; + } + + auto v = object->getProperty (unescaped); + return updatePointer (v, tail, newValue); + } + else if (auto* array = v.getArray()) + { + const auto index = [&]() -> size_t + { + if (unescaped == "-") + return (size_t) array->size(); + + if (unescaped == "0") + return 0; + + if (! unescaped.startsWith ("0")) + return (size_t) unescaped.getLargeIntValue(); + + return std::numeric_limits::max(); + }(); + + if (tail.isEmpty()) + { + if (isPositiveAndBelow (index, array->size())) + { + array->set (int (index), newValue); + return true; + } + + if (index == array->size()) + { + array->add (newValue); + return true; + } + } + + auto v = (*array)[(int) index]; + return updatePointer (v, tail, newValue); + } + + return false; +} + var JSONUtils::getPointer (const var& v, String pointer, const var& defaultValue) { if (pointer.isEmpty()) diff --git a/modules/juce_core/json/juce_JSONUtils.h b/modules/juce_core/json/juce_JSONUtils.h index 16c0bdb6b5..dc6d69dc9a 100644 --- a/modules/juce_core/json/juce_JSONUtils.h +++ b/modules/juce_core/json/juce_JSONUtils.h @@ -57,6 +57,18 @@ struct JSONUtils */ static std::optional setPointer (const var& v, String pointer, const var& newValue); + /** Given a JSON array/object 'v', a string representing a JSON pointer, + and a new property value 'newValue', updates 'v' where the + property or array index referenced by the pointer has been set to 'newValue'. + + If the pointer cannot be followed, due to referencing missing array indices + or fields, then this returns false. + + For more details, check the JSON Pointer RFC 6901: + https://datatracker.ietf.org/doc/html/rfc6901 + */ + static bool updatePointer (var& v, String pointer, const var& newValue); + /** Given a JSON array/object 'v', a string representing a JSON pointer, returns the value of the property or array index referenced by the pointer From eddf488258d3b782ac2276b902d4cd3498436eb2 Mon Sep 17 00:00:00 2001 From: Roland Rabien Date: Sat, 28 Dec 2024 08:32:57 -0800 Subject: [PATCH 6/6] Make API more jucey --- modules/juce_core/containers/juce_Variant.cpp | 6 +++--- modules/juce_core/containers/juce_Variant.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/juce_core/containers/juce_Variant.cpp b/modules/juce_core/containers/juce_Variant.cpp index 62181c778b..b0f9e9d34f 100644 --- a/modules/juce_core/containers/juce_Variant.cpp +++ b/modules/juce_core/containers/juce_Variant.cpp @@ -708,14 +708,14 @@ bool var::hasProperty (const Identifier& propertyName) const noexcept return false; } -juce::StringArray var::getProperties() const +Array var::getProperties() const { if (auto* o = getDynamicObject()) { - juce::StringArray names; + Array names; for (auto itr : o->getProperties()) - names.add (itr.name.toString()); + names.add (itr.name); return names; } diff --git a/modules/juce_core/containers/juce_Variant.h b/modules/juce_core/containers/juce_Variant.h index be5fa893a6..653cb8db0d 100644 --- a/modules/juce_core/containers/juce_Variant.h +++ b/modules/juce_core/containers/juce_Variant.h @@ -263,7 +263,7 @@ public: /** Returns true if this variant is an object and if it has the given property. */ bool hasProperty (const Identifier& propertyName) const noexcept; /** Returns property names if this variant is an object. */ - juce::StringArray getProperties() const; + Array getProperties() const; /** Invokes a named method call with no arguments. */ var call (const Identifier& method) const;