OpenShot Audio Library | OpenShotAudio  0.3.1
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 
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  }
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 //==============================================================================
305 struct JSONFormatter
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 
520 Result JSON::parseQuotedString (String::CharPointerType& t, var& result)
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
static size_t getBytesRequiredFor(juce_wchar charToWrite) noexcept
static bool canRepresent(juce_wchar character) noexcept
static double readDoubleValue(CharPointerType &text) noexcept
static int getHexDigitValue(juce_wchar digit) noexcept
static bool isWhitespace(char character) noexcept
String loadFileAsString() const
Definition: juce_File.cpp:550
virtual String readEntireStreamAsString()
static var fromString(StringRef)
Definition: juce_JSON.cpp:466
static Result parse(const String &text, var &parsedResult)
Definition: juce_JSON.cpp:487
static String escapeString(StringRef)
Definition: juce_JSON.cpp:513
static String toString(const var &objectToFormat, bool allOnOneLine=false, int maximumDecimalPlaces=15)
Definition: juce_JSON.cpp:501
static void writeToStream(OutputStream &output, const var &objectToFormat, bool allOnOneLine=false, int maximumDecimalPlaces=15)
Definition: juce_JSON.cpp:508
static Result parseQuotedString(String::CharPointerType &text, var &result)
Definition: juce_JSON.cpp:520
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:1198
String paddedLeft(juce_wchar padCharacter, int minimumLength) const
static String toHexString(IntegerType number)
Definition: juce_String.h:1053