OpenShot Audio Library | OpenShotAudio  0.6.0
juce_JSON.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 struct JSONParser
27 {
28  JSONParser (String::CharPointerType text) : startLocation (text), currentLocation (text) {}
29 
30  String::CharPointerType startLocation, currentLocation;
31 
32  struct ErrorException
33  {
34  String message;
35  int line = 1, column = 1;
36 
37  String getDescription() const { return String (line) + ":" + String (column) + ": error: " + message; }
38  Result getResult() const { return Result::fail (getDescription()); }
39  };
40 
41  [[noreturn]] void throwError (juce::String message, String::CharPointerType location)
42  {
43  ErrorException e;
44  e.message = std::move (message);
45 
46  for (auto i = startLocation; i < location && ! i.isEmpty(); ++i)
47  {
48  ++e.column;
49  if (*i == '\n') { e.column = 1; e.line++; }
50  }
51 
52  throw e;
53  }
54 
55  void skipWhitespace() { currentLocation = currentLocation.findEndOfWhitespace(); }
56  juce_wchar readChar() { return currentLocation.getAndAdvance(); }
57  juce_wchar peekChar() const { return *currentLocation; }
58  bool matchIf (char c) { if (peekChar() == (juce_wchar) c) { ++currentLocation; return true; } return false; }
59  bool isEOF() const { return peekChar() == 0; }
60 
61  bool matchString (const char* t)
62  {
63  while (*t != 0)
64  if (! matchIf (*t++))
65  return false;
66 
67  return true;
68  }
69 
70  var parseObjectOrArray()
71  {
72  skipWhitespace();
73 
74  if (matchIf ('{')) return parseObject();
75  if (matchIf ('[')) return parseArray();
76 
77  if (! isEOF())
78  throwError ("Expected '{' or '['", currentLocation);
79 
80  return {};
81  }
82 
83  String parseString (const juce_wchar quoteChar)
84  {
85  MemoryOutputStream buffer (256);
86 
87  for (;;)
88  {
89  auto c = readChar();
90 
91  if (c == quoteChar)
92  break;
93 
94  if (c == '\\')
95  {
96  auto errorLocation = currentLocation;
97  c = readChar();
98 
99  switch (c)
100  {
101  case '"':
102  case '\'':
103  case '\\':
104  case '/': break;
105 
106  case 'a': c = '\a'; break;
107  case 'b': c = '\b'; break;
108  case 'f': c = '\f'; break;
109  case 'n': c = '\n'; break;
110  case 'r': c = '\r'; break;
111  case 't': c = '\t'; break;
112 
113  case 'u':
114  {
115  c = 0;
116 
117  for (int i = 4; --i >= 0;)
118  {
119  auto digitValue = CharacterFunctions::getHexDigitValue (readChar());
120 
121  if (digitValue < 0)
122  throwError ("Syntax error in unicode escape sequence", errorLocation);
123 
124  c = (juce_wchar) ((c << 4) + static_cast<juce_wchar> (digitValue));
125  }
126 
127  break;
128  }
129 
130  default: break;
131  }
132  }
133 
134  if (c == 0)
135  throwError ("Unexpected EOF in string constant", currentLocation);
136 
137  buffer.appendUTF8Char (c);
138  }
139 
140  return buffer.toUTF8();
141  }
142 
143  var parseAny()
144  {
145  skipWhitespace();
146  auto originalLocation = currentLocation;
147 
148  switch (readChar())
149  {
150  case '{': return parseObject();
151  case '[': return parseArray();
152  case '"': return parseString ('"');
153  case '\'': return parseString ('\'');
154 
155  case '-':
156  skipWhitespace();
157  return parseNumber (true);
158 
159  case '0': case '1': case '2': case '3': case '4':
160  case '5': case '6': case '7': case '8': case '9':
161  currentLocation = originalLocation;
162  return parseNumber (false);
163 
164  case 't': // "true"
165  if (matchString ("rue"))
166  return var (true);
167 
168  break;
169 
170  case 'f': // "false"
171  if (matchString ("alse"))
172  return var (false);
173 
174  break;
175 
176  case 'n': // "null"
177  if (matchString ("ull"))
178  return {};
179 
180  break;
181 
182  default:
183  break;
184  }
185 
186  throwError ("Syntax error", originalLocation);
187  }
188 
189  var parseNumber (bool isNegative)
190  {
191  auto originalPos = currentLocation;
192 
193  int64 intValue = readChar() - '0';
194  jassert (intValue >= 0 && intValue < 10);
195 
196  for (;;)
197  {
198  auto lastPos = currentLocation;
199  auto c = readChar();
200  auto digit = ((int) c) - '0';
201 
202  if (isPositiveAndBelow (digit, 10))
203  {
204  intValue = intValue * 10 + digit;
205  continue;
206  }
207 
208  if (c == 'e' || c == 'E' || c == '.')
209  {
210  currentLocation = originalPos;
211  auto asDouble = CharacterFunctions::readDoubleValue (currentLocation);
212  return var (isNegative ? -asDouble : asDouble);
213  }
214 
216  || c == ',' || c == '}' || c == ']' || c == 0)
217  {
218  currentLocation = lastPos;
219  break;
220  }
221 
222  throwError ("Syntax error in number", lastPos);
223  }
224 
225  auto correctedValue = isNegative ? -intValue : intValue;
226 
227  return (intValue >> 31) != 0 ? var (correctedValue)
228  : var ((int) correctedValue);
229  }
230 
231  var parseObject()
232  {
233  auto resultObject = new DynamicObject();
234  var result (resultObject);
235  auto& resultProperties = resultObject->getProperties();
236  auto startOfObjectDecl = currentLocation;
237 
238  for (;;)
239  {
240  skipWhitespace();
241  auto errorLocation = currentLocation;
242  auto c = readChar();
243 
244  if (c == '}')
245  break;
246 
247  if (c == 0)
248  throwError ("Unexpected EOF in object declaration", startOfObjectDecl);
249 
250  if (c != '"')
251  throwError ("Expected a property name in double-quotes", errorLocation);
252 
253  errorLocation = currentLocation;
254  Identifier propertyName (parseString ('"'));
255 
256  if (! propertyName.isValid())
257  throwError ("Invalid property name", errorLocation);
258 
259  skipWhitespace();
260  errorLocation = currentLocation;
261 
262  if (readChar() != ':')
263  throwError ("Expected ':'", errorLocation);
264 
265  resultProperties.set (propertyName, parseAny());
266 
267  skipWhitespace();
268  if (matchIf (',')) continue;
269  if (matchIf ('}')) break;
270 
271  throwError ("Expected ',' or '}'", currentLocation);
272  }
273 
274  return result;
275  }
276 
277  var parseArray()
278  {
279  auto result = var (Array<var>());
280  auto destArray = result.getArray();
281  auto startOfArrayDecl = currentLocation;
282 
283  for (;;)
284  {
285  skipWhitespace();
286 
287  if (matchIf (']'))
288  break;
289 
290  if (isEOF())
291  throwError ("Unexpected EOF in array declaration", startOfArrayDecl);
292 
293  destArray->add (parseAny());
294  skipWhitespace();
295 
296  if (matchIf (',')) continue;
297  if (matchIf (']')) break;
298 
299  throwError ("Expected ',' or ']'", currentLocation);
300  }
301 
302  return result;
303  }
304 };
305 
306 //==============================================================================
307 struct JSONFormatter
308 {
309  static void writeEscapedChar (OutputStream& out, const unsigned short value)
310  {
311  out << "\\u" << String::toHexString ((int) value).paddedLeft ('0', 4);
312  }
313 
314  static void writeString (OutputStream& out, String::CharPointerType t)
315  {
316  for (;;)
317  {
318  auto c = t.getAndAdvance();
319 
320  switch (c)
321  {
322  case 0: return;
323 
324  case '\"': out << "\\\""; break;
325  case '\\': out << "\\\\"; break;
326  case '\a': out << "\\a"; break;
327  case '\b': out << "\\b"; break;
328  case '\f': out << "\\f"; break;
329  case '\t': out << "\\t"; break;
330  case '\r': out << "\\r"; break;
331  case '\n': out << "\\n"; break;
332 
333  default:
334  if (c >= 32 && c < 127)
335  {
336  out << (char) c;
337  }
338  else
339  {
341  {
342  CharPointer_UTF16::CharType chars[2];
343  CharPointer_UTF16 utf16 (chars);
344  utf16.write (c);
345 
346  for (int i = 0; i < 2; ++i)
347  writeEscapedChar (out, (unsigned short) chars[i]);
348  }
349  else
350  {
351  writeEscapedChar (out, (unsigned short) c);
352  }
353  }
354 
355  break;
356  }
357  }
358  }
359 
360  static void writeSpaces (OutputStream& out, int numSpaces)
361  {
362  out.writeRepeatedByte (' ', (size_t) numSpaces);
363  }
364 
365  static void writeArray (OutputStream& out, const Array<var>& array, const JSON::FormatOptions& format)
366  {
367  out << '[';
368 
369  if (! array.isEmpty())
370  {
371  if (format.getSpacing() == JSON::Spacing::multiLine)
372  out << newLine;
373 
374  for (int i = 0; i < array.size(); ++i)
375  {
376  if (format.getSpacing() == JSON::Spacing::multiLine)
377  writeSpaces (out, format.getIndentLevel() + indentSize);
378 
379  JSON::writeToStream (out, array.getReference (i), format.withIndentLevel (format.getIndentLevel() + indentSize));
380 
381  if (i < array.size() - 1)
382  {
383  out << ",";
384 
385  switch (format.getSpacing())
386  {
387  case JSON::Spacing::none: break;
388  case JSON::Spacing::singleLine: out << ' '; break;
389  case JSON::Spacing::multiLine: out << newLine; break;
390  }
391  }
392  else if (format.getSpacing() == JSON::Spacing::multiLine)
393  out << newLine;
394  }
395 
396  if (format.getSpacing() == JSON::Spacing::multiLine)
397  writeSpaces (out, format.getIndentLevel());
398  }
399 
400  out << ']';
401  }
402 
403  enum { indentSize = 2 };
404 };
405 
406 
407 void JSON::writeToStream (OutputStream& out, const var& v, const FormatOptions& opt)
408 {
409  if (v.isString())
410  {
411  out << '"';
412  JSONFormatter::writeString (out, v.toString().getCharPointer());
413  out << '"';
414  }
415  else if (v.isVoid())
416  {
417  out << "null";
418  }
419  else if (v.isUndefined())
420  {
421  out << "undefined";
422  }
423  else if (v.isBool())
424  {
425  out << (static_cast<bool> (v) ? "true" : "false");
426  }
427  else if (v.isDouble())
428  {
429  auto d = static_cast<double> (v);
430 
431  if (juce_isfinite (d))
432  {
433  out << serialiseDouble (d);
434  }
435  else
436  {
437  out << "null";
438  }
439  }
440  else if (v.isArray())
441  {
442  JSONFormatter::writeArray (out, *v.getArray(), opt);
443  }
444  else if (v.isObject())
445  {
446  if (auto* object = v.getDynamicObject())
447  object->writeAsJSON (out, opt);
448  else
449  jassertfalse; // Only DynamicObjects can be converted to JSON!
450  }
451  else
452  {
453  // Can't convert these other types of object to JSON!
454  jassert (! (v.isMethod() || v.isBinaryData()));
455 
456  out << v.toString();
457  }
458 }
459 
460 String JSON::toString (const var& v, const FormatOptions& opt)
461 {
462  MemoryOutputStream mo { 1024 };
463  writeToStream (mo, v, opt);
464  return mo.toUTF8();
465 }
466 
467 //==============================================================================
468 var JSON::parse (const String& text)
469 {
470  var result;
471 
472  if (parse (text, result))
473  return result;
474 
475  return {};
476 }
477 
479 {
480  try
481  {
482  return JSONParser (text.text).parseAny();
483  }
484  catch (const JSONParser::ErrorException&) {}
485 
486  return {};
487 }
488 
490 {
491  return parse (input.readEntireStreamAsString());
492 }
493 
494 var JSON::parse (const File& file)
495 {
496  return parse (file.loadFileAsString());
497 }
498 
499 Result JSON::parse (const String& text, var& result)
500 {
501  try
502  {
503  result = JSONParser (text.getCharPointer()).parseObjectOrArray();
504  }
505  catch (const JSONParser::ErrorException& error)
506  {
507  return error.getResult();
508  }
509 
510  return Result::ok();
511 }
512 
513 String JSON::toString (const var& data, const bool allOnOneLine, int maximumDecimalPlaces)
514 {
515  return toString (data, FormatOptions{}.withSpacing (allOnOneLine ? Spacing::singleLine : Spacing::multiLine)
516  .withMaxDecimalPlaces (maximumDecimalPlaces));
517 }
518 
519 void JSON::writeToStream (OutputStream& output, const var& data, const bool allOnOneLine, int maximumDecimalPlaces)
520 {
522  .withMaxDecimalPlaces (maximumDecimalPlaces));
523 }
524 
526 {
528  JSONFormatter::writeString (mo, s.text);
529  return mo.toString();
530 }
531 
532 Result JSON::parseQuotedString (String::CharPointerType& t, var& result)
533 {
534  try
535  {
536  JSONParser parser (t);
537  auto quote = parser.readChar();
538 
539  if (quote != '"' && quote != '\'')
540  return Result::fail ("Not a quoted string!");
541 
542  result = parser.parseString (quote);
543  t = parser.currentLocation;
544  }
545  catch (const JSONParser::ErrorException& error)
546  {
547  return error.getResult();
548  }
549 
550  return Result::ok();
551 }
552 
553 
554 //==============================================================================
555 //==============================================================================
556 #if JUCE_UNIT_TESTS
557 
558 class JSONTests final : public UnitTest
559 {
560 public:
561  JSONTests()
562  : UnitTest ("JSON", UnitTestCategories::json)
563  {}
564 
565  static String createRandomWideCharString (Random& r)
566  {
567  juce_wchar buffer[40] = { 0 };
568 
569  for (int i = 0; i < numElementsInArray (buffer) - 1; ++i)
570  {
571  if (r.nextBool())
572  {
573  do
574  {
575  buffer[i] = (juce_wchar) (1 + r.nextInt (0x10ffff - 1));
576  }
577  while (! CharPointer_UTF16::canRepresent (buffer[i]));
578  }
579  else
580  buffer[i] = (juce_wchar) (1 + r.nextInt (0xff));
581  }
582 
583  return CharPointer_UTF32 (buffer);
584  }
585 
586  static String createRandomIdentifier (Random& r)
587  {
588  char buffer[30] = { 0 };
589 
590  for (int i = 0; i < numElementsInArray (buffer) - 1; ++i)
591  {
592  static const char chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-:";
593  buffer[i] = chars [r.nextInt (sizeof (chars) - 1)];
594  }
595 
596  return CharPointer_ASCII (buffer);
597  }
598 
599  // Creates a random double that can be easily stringified, to avoid
600  // false failures when decimal places are rounded or truncated slightly
601  static var createRandomDouble (Random& r)
602  {
603  return var ((r.nextDouble() * 1000.0) + 0.1);
604  }
605 
606  static var createRandomVar (Random& r, int depth)
607  {
608  switch (r.nextInt (depth > 3 ? 6 : 8))
609  {
610  case 0: return {};
611  case 1: return r.nextInt();
612  case 2: return r.nextInt64();
613  case 3: return r.nextBool();
614  case 4: return createRandomDouble (r);
615  case 5: return createRandomWideCharString (r);
616 
617  case 6:
618  {
619  var v (createRandomVar (r, depth + 1));
620 
621  for (int i = 1 + r.nextInt (30); --i >= 0;)
622  v.append (createRandomVar (r, depth + 1));
623 
624  return v;
625  }
626 
627  case 7:
628  {
629  auto o = new DynamicObject();
630 
631  for (int i = r.nextInt (30); --i >= 0;)
632  o->setProperty (createRandomIdentifier (r), createRandomVar (r, depth + 1));
633 
634  return o;
635  }
636 
637  default:
638  return {};
639  }
640  }
641 
642  void runTest() override
643  {
644  {
645  beginTest ("JSON");
646 
647  auto r = getRandom();
648 
649  expect (JSON::parse (String()) == var());
650  expect (JSON::parse ("{}").isObject());
651  expect (JSON::parse ("[]").isArray());
652  expect (JSON::parse ("[ 1234 ]")[0].isInt());
653  expect (JSON::parse ("[ 12345678901234 ]")[0].isInt64());
654  expect (JSON::parse ("[ 1.123e3 ]")[0].isDouble());
655  expect (JSON::parse ("[ -1234]")[0].isInt());
656  expect (JSON::parse ("[-12345678901234]")[0].isInt64());
657  expect (JSON::parse ("[-1.123e3]")[0].isDouble());
658 
659  for (int i = 100; --i >= 0;)
660  {
661  var v;
662 
663  if (i > 0)
664  v = createRandomVar (r, 0);
665 
666  const auto oneLine = r.nextBool();
667  const auto asString = JSON::toString (v, oneLine);
668  const auto parsed = JSON::parse ("[" + asString + "]")[0];
669  const auto parsedString = JSON::toString (parsed, oneLine);
670  expect (asString.isNotEmpty() && parsedString == asString);
671  }
672  }
673 
674  {
675  beginTest ("Float formatting");
676 
677  std::map<double, String> tests;
678  tests[1] = "1.0";
679  tests[1.1] = "1.1";
680  tests[1.01] = "1.01";
681  tests[0.76378] = "0.76378";
682  tests[-10] = "-10.0";
683  tests[10.01] = "10.01";
684  tests[0.0123] = "0.0123";
685  tests[-3.7e-27] = "-3.7e-27";
686  tests[1e+40] = "1.0e40";
687  tests[-12345678901234567.0] = "-1.234567890123457e16";
688  tests[192000] = "192000.0";
689  tests[1234567] = "1.234567e6";
690  tests[0.00006] = "0.00006";
691  tests[0.000006] = "6.0e-6";
692 
693  for (auto& test : tests)
694  expectEquals (JSON::toString (test.first), test.second);
695  }
696  }
697 };
698 
699 static JSONTests JSONUnitTests;
700 
701 #endif
702 
703 } // namespace juce
static size_t getBytesRequiredFor(juce_wchar charToWrite) noexcept
static bool canRepresent(juce_wchar character) noexcept
static bool isWhitespace(char character) noexcept
static double readDoubleValue(CharPointerType &text) noexcept
static int getHexDigitValue(juce_wchar digit) noexcept
String loadFileAsString() const
Definition: juce_File.cpp:556
virtual String readEntireStreamAsString()
FormatOptions withSpacing(Spacing x) const
Definition: juce_JSON.h:105
static var fromString(StringRef)
Definition: juce_JSON.cpp:478
static Result parse(const String &text, var &parsedResult)
Definition: juce_JSON.cpp:499
static String escapeString(StringRef)
Definition: juce_JSON.cpp:525
@ none
All optional whitespace should be omitted.
@ multiLine
Newlines and spaces will be included in the output, in order to make it easy to read for humans.
@ singleLine
All output should be on a single line, but with some additional spacing, e.g. after commas and colons...
static String toString(const var &objectToFormat, bool allOnOneLine=false, int maximumDecimalPlaces=15)
Definition: juce_JSON.cpp:513
static void writeToStream(OutputStream &output, const var &objectToFormat, bool allOnOneLine=false, int maximumDecimalPlaces=15)
Definition: juce_JSON.cpp:519
static Result parseQuotedString(String::CharPointerType &text, var &result)
Definition: juce_JSON.cpp:532
static Result fail(const String &errorMessage) noexcept
Definition: juce_Result.cpp:65
static Result ok() noexcept
Definition: juce_Result.h:61
String::CharPointerType text
CharPointerType getCharPointer() const noexcept
Definition: juce_String.h:1153
String paddedLeft(juce_wchar padCharacter, int minimumLength) const
static String toHexString(IntegerType number)
Definition: juce_String.h:1097
Array< var > * getArray() const noexcept