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
23namespace juce
24{
25
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//==============================================================================
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
501String 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
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
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];
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
687static JSONTests JSONUnitTests;
688
689#endif
690
691} // namespace juce
Holds a resizable array of primitive or copy-by-value objects.
Definition: juce_Array.h:60
bool isEmpty() const noexcept
Returns true if the array is empty, false otherwise.
Definition: juce_Array.h:226
int size() const noexcept
Returns the current number of elements in the array.
Definition: juce_Array.h:219
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
Wraps a pointer to a null-terminated UTF-16 character string, and provides various methods to operate...
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...
static bool canRepresent(juce_wchar character) noexcept
Returns true if the given unicode character can be represented in this encoding.
void write(juce_wchar charToWrite) noexcept
Writes a unicode character to this string, and advances this pointer to point to the next position.
Wraps a pointer to a null-terminated UTF-8 character string, and provides various methods to operate ...
juce_wchar getAndAdvance() noexcept
Returns the character that this pointer is currently pointing to, and then advances the pointer to po...
bool isEmpty() const noexcept
Returns true if this pointer is pointing to a null character.
CharPointer_UTF8 findEndOfWhitespace() const noexcept
Returns the first non-whitespace character in the string.
static double readDoubleValue(CharPointerType &text) noexcept
Parses a character string to read a floating-point number.
static int getHexDigitValue(juce_wchar digit) noexcept
Returns 0 to 16 for '0' to 'F", or -1 for characters that aren't a legal hex digit.
static bool isWhitespace(char character) noexcept
Checks whether a character is whitespace.
Represents a local file or directory.
Definition: juce_File.h:45
String loadFileAsString() const
Reads a file into memory as a string.
Definition: juce_File.cpp:550
The base class for streams that read data.
virtual String readEntireStreamAsString()
Tries to read the whole stream and turn it into a string.
static var fromString(StringRef)
Parses a string that was created with the toString() method.
Definition: juce_JSON.cpp:466
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
static String escapeString(StringRef)
Returns a version of a string with any extended characters escaped.
Definition: juce_JSON.cpp:513
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 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 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
Writes data to an internal memory buffer, which grows as required.
String toUTF8() const
Returns a String created from the (UTF8) data that has been written to the stream.
String toString() const
Attempts to detect the encoding of the data and convert it to a string.
The base class for streams that write data to some kind of destination.
virtual bool writeRepeatedByte(uint8 byte, size_t numTimesToRepeat)
Writes a byte to the output stream a given number of times.
Represents the 'success' or 'failure' of an operation, and holds an associated error message to descr...
Definition: juce_Result.h:61
static Result fail(const String &errorMessage) noexcept
Creates a 'failure' result.
Definition: juce_Result.cpp:65
static Result ok() noexcept
Creates and returns a 'successful' result.
Definition: juce_Result.h:65
A simple class for holding temporary references to a string literal or String.
String::CharPointerType text
The text that is referenced.
The JUCE String class!
Definition: juce_String.h:43
CharPointerType getCharPointer() const noexcept
Returns the character pointer currently being used to store this string.
Definition: juce_String.h:1202
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...
static String toHexString(IntegerType number)
Returns a string representing this numeric value in hexadecimal.
Definition: juce_String.h:1057
This is a base class for classes that perform a unit test.
Definition: juce_UnitTest.h:74
A variant class, that can be used to hold a range of primitive values.
Definition: juce_Variant.h:46
Array< var > * getArray() const noexcept
If this variant holds an array, this provides access to it.