OpenShot Audio Library | OpenShotAudio 0.4.0
 
Loading...
Searching...
No Matches
juce_JSONUtils.cpp
1/*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 The code included in this file is provided under the terms of the ISC license
11 http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12 To use, copy, modify, and/or distribute this software for any purpose with or
13 without fee is hereby granted provided that the above copyright notice and
14 this permission notice appear in all copies.
15
16 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18 DISCLAIMED.
19
20 ==============================================================================
21*/
22
23namespace juce
24{
25
26var JSONUtils::makeObject (const std::map<Identifier, var>& source)
27{
28 auto result = std::make_unique<DynamicObject>();
29
30 for (const auto& [name, value] : source)
31 result->setProperty (name, value);
32
33 return var (result.release());
34}
35
36var JSONUtils::makeObjectWithKeyFirst (const std::map<Identifier, var>& source,
37 Identifier key)
38{
39 auto result = std::make_unique<DynamicObject>();
40
41 if (const auto iter = source.find (key); iter != source.end())
42 result->setProperty (key, iter->second);
43
44 for (const auto& [name, value] : source)
45 if (name != key)
46 result->setProperty (name, value);
47
48 return var (result.release());
49}
50
51std::optional<var> JSONUtils::setPointer (const var& v,
52 String pointer,
53 const var& newValue)
54{
55 if (pointer.isEmpty())
56 return newValue;
57
58 if (! pointer.startsWith ("/"))
59 {
60 // This is not a well-formed JSON pointer
61 jassertfalse;
62 return {};
63 }
64
65 const auto findResult = pointer.indexOfChar (1, '/');
66 const auto pos = findResult < 0 ? pointer.length() : findResult;
67 const String head (pointer.begin() + 1, pointer.begin() + pos);
68 const String tail (pointer.begin() + pos, pointer.end());
69
70 const auto unescaped = head.replace ("~1", "/").replace ("~0", "~");
71
72 if (auto* object = v.getDynamicObject())
73 {
74 if (const auto newProperty = setPointer (object->getProperty (unescaped), tail, newValue))
75 {
76 auto cloned = object->clone();
77 cloned->setProperty (unescaped, *newProperty);
78 return var (cloned.release());
79 }
80 }
81 else if (auto* array = v.getArray())
82 {
83 const auto index = [&]() -> size_t
84 {
85 if (unescaped == "-")
86 return (size_t) array->size();
87
88 if (unescaped == "0")
89 return 0;
90
91 if (! unescaped.startsWith ("0"))
92 return (size_t) unescaped.getLargeIntValue();
93
94 return std::numeric_limits<size_t>::max();
95 }();
96
97 if (const auto newIndex = setPointer ((*array)[(int) index], tail, newValue))
98 {
99 auto copied = *array;
100
101 if ((int) index == copied.size())
102 copied.add ({});
103
104 if (isPositiveAndBelow (index, copied.size()))
105 {
106 copied.getReference ((int) index) = *newIndex;
107 return var (copied);
108 }
109 }
110 }
111
112 return {};
113}
114
115bool JSONUtils::deepEqual (const var& a, const var& b)
116{
117 const auto compareObjects = [] (const DynamicObject& x, const DynamicObject& y)
118 {
119 if (x.getProperties().size() != y.getProperties().size())
120 return false;
121
122 for (const auto& [key, value] : x.getProperties())
123 {
124 if (! y.hasProperty (key))
125 return false;
126
127 if (! deepEqual (value, y.getProperty (key)))
128 return false;
129 }
130
131 return true;
132 };
133
134 if (auto* i = a.getDynamicObject())
135 if (auto* j = b.getDynamicObject())
136 return compareObjects (*i, *j);
137
138 if (auto* i = a.getArray())
139 if (auto* j = b.getArray())
140 return std::equal (i->begin(), i->end(), j->begin(), j->end(), [] (const var& x, const var& y) { return deepEqual (x, y); });
141
142 return a == b;
143}
144
145//==============================================================================
146//==============================================================================
147#if JUCE_UNIT_TESTS
148
149class JSONUtilsTests final : public UnitTest
150{
151public:
152 JSONUtilsTests() : UnitTest ("JSONUtils", UnitTestCategories::json) {}
153
154 void runTest() override
155 {
156 beginTest ("JSON pointers");
157 {
158 const auto obj = JSON::parse (R"({ "name": "PIANO 4"
159 , "lfoSpeed": 30
160 , "lfoWaveform": "triangle"
161 , "pitchEnvelope": { "rates": [94,67,95,60], "levels": [50,50,50,50] }
162 })");
163 expectDeepEqual (JSONUtils::setPointer (obj, "", "hello world"), var ("hello world"));
164 expectDeepEqual (JSONUtils::setPointer (obj, "/lfoWaveform/foobar", "str"), std::nullopt);
165 expectDeepEqual (JSONUtils::setPointer (JSON::parse (R"({"foo":0,"bar":1})"), "/foo", 2), JSON::parse (R"({"foo":2,"bar":1})"));
166 expectDeepEqual (JSONUtils::setPointer (JSON::parse (R"({"foo":0,"bar":1})"), "/baz", 2), JSON::parse (R"({"foo":0,"bar":1,"baz":2})"));
167 expectDeepEqual (JSONUtils::setPointer (JSON::parse (R"({"foo":{},"bar":{}})"), "/foo/bar", 2), JSON::parse (R"({"foo":{"bar":2},"bar":{}})"));
168 expectDeepEqual (JSONUtils::setPointer (obj, "/pitchEnvelope/rates/01", "str"), std::nullopt);
169 expectDeepEqual (JSONUtils::setPointer (obj, "/pitchEnvelope/rates/10", "str"), std::nullopt);
170 expectDeepEqual (JSONUtils::setPointer (obj, "/lfoSpeed", 10), JSON::parse (R"({ "name": "PIANO 4"
171 , "lfoSpeed": 10
172 , "lfoWaveform": "triangle"
173 , "pitchEnvelope": { "rates": [94,67,95,60], "levels": [50,50,50,50] }
174 })"));
175 expectDeepEqual (JSONUtils::setPointer (JSON::parse (R"([0,1,2])"), "/0", "bang"), JSON::parse (R"(["bang",1,2])"));
176 expectDeepEqual (JSONUtils::setPointer (JSON::parse (R"([0,1,2])"), "/0", "bang"), JSON::parse (R"(["bang",1,2])"));
177 expectDeepEqual (JSONUtils::setPointer (JSON::parse (R"({"/":"fizz"})"), "/~1", "buzz"), JSON::parse (R"({"/":"buzz"})"));
178 expectDeepEqual (JSONUtils::setPointer (JSON::parse (R"({"~":"fizz"})"), "/~0", "buzz"), JSON::parse (R"({"~":"buzz"})"));
179 expectDeepEqual (JSONUtils::setPointer (obj, "/pitchEnvelope/rates/0", 80), JSON::parse (R"({ "name": "PIANO 4"
180 , "lfoSpeed": 30
181 , "lfoWaveform": "triangle"
182 , "pitchEnvelope": { "rates": [80,67,95,60], "levels": [50,50,50,50] }
183 })"));
184 expectDeepEqual (JSONUtils::setPointer (obj, "/pitchEnvelope/levels/0", 80), JSON::parse (R"({ "name": "PIANO 4"
185 , "lfoSpeed": 30
186 , "lfoWaveform": "triangle"
187 , "pitchEnvelope": { "rates": [94,67,95,60], "levels": [80,50,50,50] }
188 })"));
189 expectDeepEqual (JSONUtils::setPointer (obj, "/pitchEnvelope/levels/-", 100), JSON::parse (R"({ "name": "PIANO 4"
190 , "lfoSpeed": 30
191 , "lfoWaveform": "triangle"
192 , "pitchEnvelope": { "rates": [94,67,95,60], "levels": [50,50,50,50,100] }
193 })"));
194 }
195 }
196
197 void expectDeepEqual (const std::optional<var>& a, const std::optional<var>& b)
198 {
199 const auto text = a.has_value() && b.has_value()
200 ? JSON::toString (*a) + " != " + JSON::toString (*b)
201 : String();
202 expect (deepEqual (a, b), text);
203 }
204
205 static bool deepEqual (const std::optional<var>& a, const std::optional<var>& b)
206 {
207 if (a.has_value() && b.has_value())
208 return JSONUtils::deepEqual (*a, *b);
209
210 return a == b;
211 }
212};
213
214static JSONUtilsTests jsonUtilsTests;
215
216#endif
217
218} // namespace juce
NamedValueSet & getProperties() noexcept
int size() const noexcept
CharPointerType begin() const
int indexOfChar(juce_wchar characterToLookFor) const noexcept
int length() const noexcept
bool isEmpty() const noexcept
bool startsWith(StringRef text) const noexcept
CharPointerType end() const
String replace(StringRef stringToReplace, StringRef stringToInsertInstead, bool ignoreCase=false) const
Array< var > * getArray() const noexcept
static std::optional< var > setPointer(const var &v, String pointer, const var &newValue)
static bool deepEqual(const var &a, const var &b)
static var makeObject(const std::map< Identifier, var > &source)
static var makeObjectWithKeyFirst(const std::map< Identifier, var > &source, Identifier key)