Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Figbug/variantimprovements #1474

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions modules/juce_core/containers/juce_Variant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,21 @@ bool var::hasProperty (const Identifier& propertyName) const noexcept
return false;
}

Array<Identifier> var::getProperties() const
{
if (auto* o = getDynamicObject())
{
Array<Identifier> names;

for (auto itr : o->getProperties())
names.add (itr.name);

return names;
}

return {};
}

var::NativeFunction var::getNativeFunction() const
{
return isMethod() && (value.methodValue != nullptr) ? *value.methodValue : nullptr;
Expand Down Expand Up @@ -893,6 +908,75 @@ 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

Expand Down
40 changes: 40 additions & 0 deletions modules/juce_core/containers/juce_Variant.h
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,8 @@ class JUCE_API var
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. */
Array<Identifier> getProperties() const;

/** Invokes a named method call with no arguments. */
var call (const Identifier& method) const;
Expand Down Expand Up @@ -352,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
126 changes: 126 additions & 0 deletions modules/juce_core/json/juce_JSONUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,125 @@ std::optional<var> 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<size_t>::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())
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<size_t>::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)
Expand Down Expand Up @@ -172,6 +291,13 @@ class JSONUtilsTests final : public UnitTest
, "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})"));
Expand Down
23 changes: 23 additions & 0 deletions modules/juce_core/json/juce_JSONUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,29 @@ struct JSONUtils
*/
static std::optional<var> 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

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<Identifier, var>& source);

Expand Down
Loading