OpenShot Audio Library | OpenShotAudio 0.3.2
Loading...
Searching...
No Matches
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
23namespace juce
24{
25
26struct 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//==============================================================================
305struct 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//==============================================================================
456var 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
482var JSON::parse (const File& file)
483{
484 return parse (file.loadFileAsString());
485}
486
487Result 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
502{
503 MemoryOutputStream mo (1024);
504 JSONFormatter::write (mo, data, 0, allOnOneLine, maximumDecimalPlaces);
505 return mo.toUTF8();
506}
507
508void 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
520Result 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
546class JSONTests : public UnitTest
547{
548public:
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];
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
687static JSONTests JSONUnitTests;
688
689#endif
690
691} // namespace juce
Array()=default
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
virtual String readEntireStreamAsString()
static var fromString(StringRef)
static Result parse(const String &text, var &parsedResult)
static String escapeString(StringRef)
static String toString(const var &objectToFormat, bool allOnOneLine=false, int maximumDecimalPlaces=15)
static void writeToStream(OutputStream &output, const var &objectToFormat, bool allOnOneLine=false, int maximumDecimalPlaces=15)
static Result parseQuotedString(String::CharPointerType &text, var &result)
static Result fail(const String &errorMessage) noexcept
static Result ok() noexcept
Definition juce_Result.h:61
String::CharPointerType text
CharPointerType getCharPointer() const noexcept
static String toHexString(IntegerType number)