OpenShot Library | OpenShotAudio  0.2.2
juce_JSON.cpp
1 /*
2  ==============================================================================
3 
4  This file is part of the JUCE library.
5  Copyright (c) 2017 - ROLI Ltd.
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 
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  {
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  }
131 
132  if (c == 0)
133  throwError ("Unexpected EOF in string constant", currentLocation);
134 
135  buffer.appendUTF8Char (c);
136  }
137 
138  return buffer.toUTF8();
139  }
140 
141  var parseAny()
142  {
143  skipWhitespace();
144  auto originalLocation = currentLocation;
145 
146  switch (readChar())
147  {
148  case '{': return parseObject();
149  case '[': return parseArray();
150  case '"': return parseString ('"');
151  case '\'': return parseString ('\'');
152 
153  case '-':
154  skipWhitespace();
155  return parseNumber (true);
156 
157  case '0': case '1': case '2': case '3': case '4':
158  case '5': case '6': case '7': case '8': case '9':
159  currentLocation = originalLocation;
160  return parseNumber (false);
161 
162  case 't': // "true"
163  if (matchString ("rue"))
164  return var (true);
165 
166  break;
167 
168  case 'f': // "false"
169  if (matchString ("alse"))
170  return var (false);
171 
172  break;
173 
174  case 'n': // "null"
175  if (matchString ("ull"))
176  return {};
177 
178  break;
179 
180  default:
181  break;
182  }
183 
184  throwError ("Syntax error", originalLocation);
185  }
186 
187  var parseNumber (bool isNegative)
188  {
189  auto originalPos = currentLocation;
190 
191  int64 intValue = readChar() - '0';
192  jassert (intValue >= 0 && intValue < 10);
193 
194  for (;;)
195  {
196  auto lastPos = currentLocation;
197  auto c = readChar();
198  auto digit = ((int) c) - '0';
199 
200  if (isPositiveAndBelow (digit, 10))
201  {
202  intValue = intValue * 10 + digit;
203  continue;
204  }
205 
206  if (c == 'e' || c == 'E' || c == '.')
207  {
208  currentLocation = originalPos;
209  auto asDouble = CharacterFunctions::readDoubleValue (currentLocation);
210  return var (isNegative ? -asDouble : asDouble);
211  }
212 
214  || c == ',' || c == '}' || c == ']' || c == 0)
215  {
216  currentLocation = lastPos;
217  break;
218  }
219 
220  throwError ("Syntax error in number", lastPos);
221  }
222 
223  auto correctedValue = isNegative ? -intValue : intValue;
224 
225  return (intValue >> 31) != 0 ? var (correctedValue)
226  : var ((int) correctedValue);
227  }
228 
229  var parseObject()
230  {
231  auto resultObject = new DynamicObject();
232  var result (resultObject);
233  auto& resultProperties = resultObject->getProperties();
234  auto startOfObjectDecl = currentLocation;
235 
236  for (;;)
237  {
238  skipWhitespace();
239  auto errorLocation = currentLocation;
240  auto c = readChar();
241 
242  if (c == '}')
243  break;
244 
245  if (c == 0)
246  throwError ("Unexpected EOF in object declaration", startOfObjectDecl);
247 
248  if (c != '"')
249  throwError ("Expected a property name in double-quotes", errorLocation);
250 
251  errorLocation = currentLocation;
252  Identifier propertyName (parseString ('"'));
253 
254  if (! propertyName.isValid())
255  throwError ("Invalid property name", errorLocation);
256 
257  skipWhitespace();
258  errorLocation = currentLocation;
259 
260  if (readChar() != ':')
261  throwError ("Expected ':'", errorLocation);
262 
263  resultProperties.set (propertyName, parseAny());
264 
265  skipWhitespace();
266  if (matchIf (',')) continue;
267  if (matchIf ('}')) break;
268 
269  throwError ("Expected ',' or '}'", currentLocation);
270  }
271 
272  return result;
273  }
274 
275  var parseArray()
276  {
277  auto result = var (Array<var>());
278  auto destArray = result.getArray();
279  auto startOfArrayDecl = currentLocation;
280 
281  for (;;)
282  {
283  skipWhitespace();
284 
285  if (matchIf (']'))
286  break;
287 
288  if (isEOF())
289  throwError ("Unexpected EOF in array declaration", startOfArrayDecl);
290 
291  destArray->add (parseAny());
292  skipWhitespace();
293 
294  if (matchIf (',')) continue;
295  if (matchIf (']')) break;
296 
297  throwError ("Expected ',' or ']'", currentLocation);
298  }
299 
300  return result;
301  }
302 };
303 
304 //==============================================================================
306 {
307  static void write (OutputStream& out, const var& v,
308  int indentLevel, bool allOnOneLine, int maximumDecimalPlaces)
309  {
310  if (v.isString())
311  {
312  out << '"';
313  writeString (out, v.toString().getCharPointer());
314  out << '"';
315  }
316  else if (v.isVoid())
317  {
318  out << "null";
319  }
320  else if (v.isUndefined())
321  {
322  out << "undefined";
323  }
324  else if (v.isBool())
325  {
326  out << (static_cast<bool> (v) ? "true" : "false");
327  }
328  else if (v.isDouble())
329  {
330  auto d = static_cast<double> (v);
331 
332  if (juce_isfinite (d))
333  {
334  out << serialiseDouble (d);
335  }
336  else
337  {
338  out << "null";
339  }
340  }
341  else if (v.isArray())
342  {
343  writeArray (out, *v.getArray(), indentLevel, allOnOneLine, maximumDecimalPlaces);
344  }
345  else if (v.isObject())
346  {
347  if (auto* object = v.getDynamicObject())
348  object->writeAsJSON (out, indentLevel, allOnOneLine, maximumDecimalPlaces);
349  else
350  jassertfalse; // Only DynamicObjects can be converted to JSON!
351  }
352  else
353  {
354  // Can't convert these other types of object to JSON!
355  jassert (! (v.isMethod() || v.isBinaryData()));
356 
357  out << v.toString();
358  }
359  }
360 
361  static void writeEscapedChar (OutputStream& out, const unsigned short value)
362  {
363  out << "\\u" << String::toHexString ((int) value).paddedLeft ('0', 4);
364  }
365 
366  static void writeString (OutputStream& out, String::CharPointerType t)
367  {
368  for (;;)
369  {
370  auto c = t.getAndAdvance();
371 
372  switch (c)
373  {
374  case 0: return;
375 
376  case '\"': out << "\\\""; break;
377  case '\\': out << "\\\\"; break;
378  case '\a': out << "\\a"; break;
379  case '\b': out << "\\b"; break;
380  case '\f': out << "\\f"; break;
381  case '\t': out << "\\t"; break;
382  case '\r': out << "\\r"; break;
383  case '\n': out << "\\n"; break;
384 
385  default:
386  if (c >= 32 && c < 127)
387  {
388  out << (char) c;
389  }
390  else
391  {
393  {
394  CharPointer_UTF16::CharType chars[2];
395  CharPointer_UTF16 utf16 (chars);
396  utf16.write (c);
397 
398  for (int i = 0; i < 2; ++i)
399  writeEscapedChar (out, (unsigned short) chars[i]);
400  }
401  else
402  {
403  writeEscapedChar (out, (unsigned short) c);
404  }
405  }
406 
407  break;
408  }
409  }
410  }
411 
412  static void writeSpaces (OutputStream& out, int numSpaces)
413  {
414  out.writeRepeatedByte (' ', (size_t) numSpaces);
415  }
416 
417  static void writeArray (OutputStream& out, const Array<var>& array,
418  int indentLevel, bool allOnOneLine, int maximumDecimalPlaces)
419  {
420  out << '[';
421 
422  if (! array.isEmpty())
423  {
424  if (! allOnOneLine)
425  out << newLine;
426 
427  for (int i = 0; i < array.size(); ++i)
428  {
429  if (! allOnOneLine)
430  writeSpaces (out, indentLevel + indentSize);
431 
432  write (out, array.getReference(i), indentLevel + indentSize, allOnOneLine, maximumDecimalPlaces);
433 
434  if (i < array.size() - 1)
435  {
436  if (allOnOneLine)
437  out << ", ";
438  else
439  out << ',' << newLine;
440  }
441  else if (! allOnOneLine)
442  out << newLine;
443  }
444 
445  if (! allOnOneLine)
446  writeSpaces (out, indentLevel);
447  }
448 
449  out << ']';
450  }
451 
452  enum { indentSize = 2 };
453 };
454 
455 //==============================================================================
456 var JSON::parse (const String& text)
457 {
458  var result;
459 
460  if (parse (text, result))
461  return result;
462 
463  return {};
464 }
465 
467 {
468  try
469  {
470  return JSONParser (text.text).parseAny();
471  }
472  catch (const JSONParser::ErrorException&) {}
473 
474  return {};
475 }
476 
478 {
479  return parse (input.readEntireStreamAsString());
480 }
481 
482 var JSON::parse (const File& file)
483 {
484  return parse (file.loadFileAsString());
485 }
486 
487 Result JSON::parse (const String& text, var& result)
488 {
489  try
490  {
491  result = JSONParser (text.getCharPointer()).parseObjectOrArray();
492  }
493  catch (const JSONParser::ErrorException& error)
494  {
495  return error.getResult();
496  }
497 
498  return Result::ok();
499 }
500 
501 String JSON::toString (const var& data, const bool allOnOneLine, int maximumDecimalPlaces)
502 {
503  MemoryOutputStream mo (1024);
504  JSONFormatter::write (mo, data, 0, allOnOneLine, maximumDecimalPlaces);
505  return mo.toUTF8();
506 }
507 
508 void JSON::writeToStream (OutputStream& output, const var& data, const bool allOnOneLine, int maximumDecimalPlaces)
509 {
510  JSONFormatter::write (output, data, 0, allOnOneLine, maximumDecimalPlaces);
511 }
512 
514 {
516  JSONFormatter::writeString (mo, s.text);
517  return mo.toString();
518 }
519 
521 {
522  try
523  {
524  JSONParser parser (t);
525  auto quote = parser.readChar();
526 
527  if (quote != '"' && quote != '\'')
528  return Result::fail ("Not a quoted string!");
529 
530  result = parser.parseString (quote);
531  t = parser.currentLocation;
532  }
533  catch (const JSONParser::ErrorException& error)
534  {
535  return error.getResult();
536  }
537 
538  return Result::ok();
539 }
540 
541 
542 //==============================================================================
543 //==============================================================================
544 #if JUCE_UNIT_TESTS
545 
546 class JSONTests : public UnitTest
547 {
548 public:
549  JSONTests()
550  : UnitTest ("JSON", UnitTestCategories::json)
551  {}
552 
553  static String createRandomWideCharString (Random& r)
554  {
555  juce_wchar buffer[40] = { 0 };
556 
557  for (int i = 0; i < numElementsInArray (buffer) - 1; ++i)
558  {
559  if (r.nextBool())
560  {
561  do
562  {
563  buffer[i] = (juce_wchar) (1 + r.nextInt (0x10ffff - 1));
564  }
565  while (! CharPointer_UTF16::canRepresent (buffer[i]));
566  }
567  else
568  buffer[i] = (juce_wchar) (1 + r.nextInt (0xff));
569  }
570 
571  return CharPointer_UTF32 (buffer);
572  }
573 
574  static String createRandomIdentifier (Random& r)
575  {
576  char buffer[30] = { 0 };
577 
578  for (int i = 0; i < numElementsInArray (buffer) - 1; ++i)
579  {
580  static const char chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-:";
581  buffer[i] = chars [r.nextInt (sizeof (chars) - 1)];
582  }
583 
584  return CharPointer_ASCII (buffer);
585  }
586 
587  // Creates a random double that can be easily stringified, to avoid
588  // false failures when decimal places are rounded or truncated slightly
589  static var createRandomDouble (Random& r)
590  {
591  return var ((r.nextDouble() * 1000.0) + 0.1);
592  }
593 
594  static var createRandomVar (Random& r, int depth)
595  {
596  switch (r.nextInt (depth > 3 ? 6 : 8))
597  {
598  case 0: return {};
599  case 1: return r.nextInt();
600  case 2: return r.nextInt64();
601  case 3: return r.nextBool();
602  case 4: return createRandomDouble (r);
603  case 5: return createRandomWideCharString (r);
604 
605  case 6:
606  {
607  var v (createRandomVar (r, depth + 1));
608 
609  for (int i = 1 + r.nextInt (30); --i >= 0;)
610  v.append (createRandomVar (r, depth + 1));
611 
612  return v;
613  }
614 
615  case 7:
616  {
617  auto o = new DynamicObject();
618 
619  for (int i = r.nextInt (30); --i >= 0;)
620  o->setProperty (createRandomIdentifier (r), createRandomVar (r, depth + 1));
621 
622  return o;
623  }
624 
625  default:
626  return {};
627  }
628  }
629 
630  void runTest() override
631  {
632  {
633  beginTest ("JSON");
634 
635  auto r = getRandom();
636 
637  expect (JSON::parse (String()) == var());
638  expect (JSON::parse ("{}").isObject());
639  expect (JSON::parse ("[]").isArray());
640  expect (JSON::parse ("[ 1234 ]")[0].isInt());
641  expect (JSON::parse ("[ 12345678901234 ]")[0].isInt64());
642  expect (JSON::parse ("[ 1.123e3 ]")[0].isDouble());
643  expect (JSON::parse ("[ -1234]")[0].isInt());
644  expect (JSON::parse ("[-12345678901234]")[0].isInt64());
645  expect (JSON::parse ("[-1.123e3]")[0].isDouble());
646 
647  for (int i = 100; --i >= 0;)
648  {
649  var v;
650 
651  if (i > 0)
652  v = createRandomVar (r, 0);
653 
654  const bool oneLine = r.nextBool();
655  String asString (JSON::toString (v, oneLine));
656  var parsed = JSON::parse ("[" + asString + "]")[0];
657  String parsedString (JSON::toString (parsed, oneLine));
658  expect (asString.isNotEmpty() && parsedString == asString);
659  }
660  }
661 
662  {
663  beginTest ("Float formatting");
664 
665  std::map<double, String> tests;
666  tests[1] = "1.0";
667  tests[1.1] = "1.1";
668  tests[1.01] = "1.01";
669  tests[0.76378] = "0.76378";
670  tests[-10] = "-10.0";
671  tests[10.01] = "10.01";
672  tests[0.0123] = "0.0123";
673  tests[-3.7e-27] = "-3.7e-27";
674  tests[1e+40] = "1.0e40";
675  tests[-12345678901234567.0] = "-1.234567890123457e16";
676  tests[192000] = "192000.0";
677  tests[1234567] = "1.234567e6";
678  tests[0.00006] = "0.00006";
679  tests[0.000006] = "6.0e-6";
680 
681  for (auto& test : tests)
682  expectEquals (JSON::toString (test.first), test.second);
683  }
684  }
685 };
686 
687 static JSONTests JSONUnitTests;
688 
689 #endif
690 
691 } // namespace juce
Wraps a pointer to a null-terminated ASCII character string, and provides various methods to operate ...
static String toString(const var &objectToFormat, bool allOnOneLine=false, int maximumDecimalPlaces=15)
Returns a string which contains a JSON-formatted representation of the var object.
Definition: juce_JSON.cpp:501
static String escapeString(StringRef)
Returns a version of a string with any extended characters escaped.
Definition: juce_JSON.cpp:513
static Result ok() noexcept
Creates and returns a &#39;successful&#39; result.
Definition: juce_Result.h:65
bool isNotEmpty() const noexcept
Returns true if the string contains at least one character.
Definition: juce_String.h:306
Represents a string identifier, designed for accessing properties by name.
int nextInt() noexcept
Returns the next random 32 bit integer.
Definition: juce_Random.cpp:78
A simple class for holding temporary references to a string literal or String.
A variant class, that can be used to hold a range of primitive values.
Definition: juce_Variant.h:45
bool nextBool() noexcept
Returns the next random boolean value.
The base class for streams that read data.
static bool canRepresent(juce_wchar character) noexcept
Returns true if the given unicode character can be represented in this encoding.
static double readDoubleValue(CharPointerType &text) noexcept
Parses a character string to read a floating-point number.
static void writeToStream(OutputStream &output, const var &objectToFormat, bool allOnOneLine=false, int maximumDecimalPlaces=15)
Writes a JSON-formatted representation of the var object to the given stream.
Definition: juce_JSON.cpp:508
static var fromString(StringRef)
Parses a string that was created with the toString() method.
Definition: juce_JSON.cpp:466
CharPointerType getCharPointer() const noexcept
Returns the character pointer currently being used to store this string.
Definition: juce_String.h:1202
bool isEmpty() const noexcept
Returns true if this pointer is pointing to a null character.
The JUCE String class!
Definition: juce_String.h:42
bool isValid() const noexcept
Returns true if this Identifier is not null.
static Result parse(const String &text, var &parsedResult)
Parses a string of JSON-formatted text, and returns a result code containing any parse errors...
Definition: juce_JSON.cpp:487
int64 nextInt64() noexcept
Returns the next 64-bit random number.
Definition: juce_Random.cpp:96
static bool isWhitespace(char character) noexcept
Checks whether a character is whitespace.
CharPointer_UTF8 findEndOfWhitespace() const noexcept
Returns the first non-whitespace character in the string.
This is a base class for classes that perform a unit test.
Definition: juce_UnitTest.h:73
static Result fail(const String &errorMessage) noexcept
Creates a &#39;failure&#39; result.
Definition: juce_Result.cpp:65
Represents a dynamically implemented object.
static Result parseQuotedString(String::CharPointerType &text, var &result)
Parses a quoted string-literal in JSON format, returning the un-escaped result in the result paramete...
Definition: juce_JSON.cpp:520
Wraps a pointer to a null-terminated UTF-32 character string, and provides various methods to operate...
String loadFileAsString() const
Reads a file into memory as a string.
Definition: juce_File.cpp:550
double nextDouble() noexcept
Returns the next random floating-point number.
void write(juce_wchar charToWrite) noexcept
Writes a unicode character to this string, and advances this pointer to point to the next position...
static int getHexDigitValue(juce_wchar digit) noexcept
Returns 0 to 16 for &#39;0&#39; to &#39;F", or -1 for characters that aren&#39;t a legal hex digit.
juce_wchar getAndAdvance() noexcept
Returns the character that this pointer is currently pointing to, and then advances the pointer to po...
static String toHexString(IntegerType number)
Returns a string representing this numeric value in hexadecimal.
Definition: juce_String.h:1057
Represents the &#39;success&#39; or &#39;failure&#39; of an operation, and holds an associated error message to descr...
Definition: juce_Result.h:60
Array< var > * getArray() const noexcept
If this variant holds an array, this provides access to it.
Represents a local file or directory.
Definition: juce_File.h:44
The base class for streams that write data to some kind of destination.
Holds a resizable array of primitive or copy-by-value objects.
Definition: juce_Array.h:59
String paddedLeft(juce_wchar padCharacter, int minimumLength) const
Returns a copy of this string with the specified character repeatedly added to its beginning until th...
int size() const noexcept
Returns the current number of elements in the array.
Definition: juce_Array.h:219
String::CharPointerType text
The text that is referenced.
static size_t getBytesRequiredFor(juce_wchar charToWrite) noexcept
Returns the number of bytes that would be needed to represent the given unicode character in this enc...
void append(const var &valueToAppend)
Appends an element to the var, converting it to an array if it isn&#39;t already one. ...
virtual bool writeRepeatedByte(uint8 byte, size_t numTimesToRepeat)
Writes a byte to the output stream a given number of times.
Wraps a pointer to a null-terminated UTF-16 character string, and provides various methods to operate...
ElementType & getReference(int index) noexcept
Returns a direct reference to one of the elements in the array, without checking the index passed in...
Definition: juce_Array.h:271
bool isEmpty() const noexcept
Returns true if the array is empty, false otherwise.
Definition: juce_Array.h:226
A random number generator.
Definition: juce_Random.h:38
bool appendUTF8Char(juce_wchar character)
Appends the utf-8 bytes for a unicode character.
String toUTF8() const
Returns a String created from the (UTF8) data that has been written to the stream.
Writes data to an internal memory buffer, which grows as required.
String toString() const
Attempts to detect the encoding of the data and convert it to a string.
virtual String readEntireStreamAsString()
Tries to read the whole stream and turn it into a string.
Wraps a pointer to a null-terminated UTF-8 character string, and provides various methods to operate ...