OpenShot Audio Library | OpenShotAudio  0.6.0
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 
23 namespace juce
24 {
25 
26 var 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 
36 var 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 
51 std::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 
115 bool 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 
149 class JSONUtilsTests final : public UnitTest
150 {
151 public:
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 
214 static JSONUtilsTests jsonUtilsTests;
215 
216 #endif
217 
218 } // namespace juce
NamedValueSet & getProperties() noexcept
static Result parse(const String &text, var &parsedResult)
Definition: juce_JSON.cpp:499
static String toString(const var &objectToFormat, bool allOnOneLine=false, int maximumDecimalPlaces=15)
Definition: juce_JSON.cpp:513
int size() const noexcept
CharPointerType begin() const
Definition: juce_String.h:923
int indexOfChar(juce_wchar characterToLookFor) const noexcept
int length() const noexcept
bool isEmpty() const noexcept
Definition: juce_String.h:310
bool startsWith(StringRef text) const noexcept
CharPointerType end() const
Definition: juce_String.h:949
String replace(StringRef stringToReplace, StringRef stringToInsertInstead, bool ignoreCase=false) const
void beginTest(const String &testName)
void expect(bool testResult, const String &failureMessage=String())
virtual void runTest()=0
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)