OpenShot Library | libopenshot-audio 0.2.0
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 static Result parseObjectOrArray (String::CharPointerType t, var& result)
29 {
30 t = t.findEndOfWhitespace();
31
32 switch (t.getAndAdvance())
33 {
34 case 0: result = var(); return Result::ok();
35 case '{': return parseObject (t, result);
36 case '[': return parseArray (t, result);
37 }
38
39 return createFail ("Expected '{' or '['", &t);
40 }
41
42 static Result parseString (const juce_wchar quoteChar, String::CharPointerType& t, var& result)
43 {
44 MemoryOutputStream buffer (256);
45
46 for (;;)
47 {
48 auto c = t.getAndAdvance();
49
50 if (c == quoteChar)
51 break;
52
53 if (c == '\\')
54 {
55 c = t.getAndAdvance();
56
57 switch (c)
58 {
59 case '"':
60 case '\'':
61 case '\\':
62 case '/': break;
63
64 case 'a': c = '\a'; break;
65 case 'b': c = '\b'; break;
66 case 'f': c = '\f'; break;
67 case 'n': c = '\n'; break;
68 case 'r': c = '\r'; break;
69 case 't': c = '\t'; break;
70
71 case 'u':
72 {
73 c = 0;
74
75 for (int i = 4; --i >= 0;)
76 {
77 auto digitValue = CharacterFunctions::getHexDigitValue (t.getAndAdvance());
78
79 if (digitValue < 0)
80 return createFail ("Syntax error in unicode escape sequence");
81
82 c = (juce_wchar) ((c << 4) + static_cast<juce_wchar> (digitValue));
83 }
84
85 break;
86 }
87 }
88 }
89
90 if (c == 0)
91 return createFail ("Unexpected end-of-input in string constant");
92
93 buffer.appendUTF8Char (c);
94 }
95
96 result = buffer.toUTF8();
97 return Result::ok();
98 }
99
100 static Result parseAny (String::CharPointerType& t, var& result)
101 {
102 t = t.findEndOfWhitespace();
103 auto t2 = t;
104
105 switch (t2.getAndAdvance())
106 {
107 case '{': t = t2; return parseObject (t, result);
108 case '[': t = t2; return parseArray (t, result);
109 case '"': t = t2; return parseString ('"', t, result);
110 case '\'': t = t2; return parseString ('\'', t, result);
111
112 case '-':
113 t2 = t2.findEndOfWhitespace();
115 break;
116
117 t = t2;
118 return parseNumber (t, result, true);
119
120 case '0': case '1': case '2': case '3': case '4':
121 case '5': case '6': case '7': case '8': case '9':
122 return parseNumber (t, result, false);
123
124 case 't': // "true"
125 if (t2.getAndAdvance() == 'r' && t2.getAndAdvance() == 'u' && t2.getAndAdvance() == 'e')
126 {
127 t = t2;
128 result = var (true);
129 return Result::ok();
130 }
131 break;
132
133 case 'f': // "false"
134 if (t2.getAndAdvance() == 'a' && t2.getAndAdvance() == 'l'
135 && t2.getAndAdvance() == 's' && t2.getAndAdvance() == 'e')
136 {
137 t = t2;
138 result = var (false);
139 return Result::ok();
140 }
141 break;
142
143 case 'n': // "null"
144 if (t2.getAndAdvance() == 'u' && t2.getAndAdvance() == 'l' && t2.getAndAdvance() == 'l')
145 {
146 t = t2;
147 result = var();
148 return Result::ok();
149 }
150 break;
151
152 default:
153 break;
154 }
155
156 return createFail ("Syntax error", &t);
157 }
158
159private:
160 static Result createFail (const char* const message, const String::CharPointerType* location = nullptr)
161 {
162 String m (message);
163 if (location != nullptr)
164 m << ": \"" << String (*location, 20) << '"';
165
166 return Result::fail (m);
167 }
168
169 static Result parseNumber (String::CharPointerType& t, var& result, const bool isNegative)
170 {
171 auto oldT = t;
172
173 int64 intValue = t.getAndAdvance() - '0';
174 jassert (intValue >= 0 && intValue < 10);
175
176 for (;;)
177 {
178 auto previousChar = t;
179 auto c = t.getAndAdvance();
180 auto digit = ((int) c) - '0';
181
182 if (isPositiveAndBelow (digit, 10))
183 {
184 intValue = intValue * 10 + digit;
185 continue;
186 }
187
188 if (c == 'e' || c == 'E' || c == '.')
189 {
190 t = oldT;
192 result = isNegative ? -asDouble : asDouble;
193 return Result::ok();
194 }
195
197 || c == ',' || c == '}' || c == ']' || c == 0)
198 {
199 t = previousChar;
200 break;
201 }
202
203 return createFail ("Syntax error in number", &oldT);
204 }
205
206 auto correctedValue = isNegative ? -intValue : intValue;
207
208 if ((intValue >> 31) != 0)
209 result = correctedValue;
210 else
211 result = (int) correctedValue;
212
213 return Result::ok();
214 }
215
216 static Result parseObject (String::CharPointerType& t, var& result)
217 {
218 auto resultObject = new DynamicObject();
219 result = resultObject;
220 auto& resultProperties = resultObject->getProperties();
221
222 for (;;)
223 {
224 t = t.findEndOfWhitespace();
225
226 auto oldT = t;
227 auto c = t.getAndAdvance();
228
229 if (c == '}')
230 break;
231
232 if (c == 0)
233 return createFail ("Unexpected end-of-input in object declaration");
234
235 if (c == '"')
236 {
238 auto r = parseString ('"', t, propertyNameVar);
239
240 if (r.failed())
241 return r;
242
243 const Identifier propertyName (propertyNameVar.toString());
244
245 if (propertyName.isValid())
246 {
247 t = t.findEndOfWhitespace();
248 oldT = t;
249
250 auto c2 = t.getAndAdvance();
251
252 if (c2 != ':')
253 return createFail ("Expected ':', but found", &oldT);
254
257
258 auto r2 = parseAny (t, *propertyValue);
259
260 if (r2.failed())
261 return r2;
262
263 t = t.findEndOfWhitespace();
264 oldT = t;
265
266 auto nextChar = t.getAndAdvance();
267
268 if (nextChar == ',')
269 continue;
270
271 if (nextChar == '}')
272 break;
273 }
274 }
275
276 return createFail ("Expected object member declaration, but found", &oldT);
277 }
278
279 return Result::ok();
280 }
281
282 static Result parseArray (String::CharPointerType& t, var& result)
283 {
284 result = var (Array<var>());
285 auto* destArray = result.getArray();
286
287 for (;;)
288 {
289 t = t.findEndOfWhitespace();
290
291 auto oldT = t;
292 auto c = t.getAndAdvance();
293
294 if (c == ']')
295 break;
296
297 if (c == 0)
298 return createFail ("Unexpected end-of-input in array declaration");
299
300 t = oldT;
301 destArray->add (var());
302 auto r = parseAny (t, destArray->getReference (destArray->size() - 1));
303
304 if (r.failed())
305 return r;
306
307 t = t.findEndOfWhitespace();
308 oldT = t;
309
310 auto nextChar = t.getAndAdvance();
311
312 if (nextChar == ',')
313 continue;
314
315 if (nextChar == ']')
316 break;
317
318 return createFail ("Expected object array item, but found", &oldT);
319 }
320
321 return Result::ok();
322 }
323};
324
325//==============================================================================
327{
328 static void write (OutputStream& out, const var& v,
330 {
331 if (v.isString())
332 {
333 out << '"';
334 writeString (out, v.toString().getCharPointer());
335 out << '"';
336 }
337 else if (v.isVoid())
338 {
339 out << "null";
340 }
341 else if (v.isUndefined())
342 {
343 out << "undefined";
344 }
345 else if (v.isBool())
346 {
347 out << (static_cast<bool> (v) ? "true" : "false");
348 }
349 else if (v.isDouble())
350 {
351 auto d = static_cast<double> (v);
352
353 if (juce_isfinite (d))
354 {
355 out << serialiseDouble (d);
356 }
357 else
358 {
359 out << "null";
360 }
361 }
362 else if (v.isArray())
363 {
364 writeArray (out, *v.getArray(), indentLevel, allOnOneLine, maximumDecimalPlaces);
365 }
366 else if (v.isObject())
367 {
368 if (auto* object = v.getDynamicObject())
369 object->writeAsJSON (out, indentLevel, allOnOneLine, maximumDecimalPlaces);
370 else
371 jassertfalse; // Only DynamicObjects can be converted to JSON!
372 }
373 else
374 {
375 // Can't convert these other types of object to JSON!
376 jassert (! (v.isMethod() || v.isBinaryData()));
377
378 out << v.toString();
379 }
380 }
381
382 static void writeEscapedChar (OutputStream& out, const unsigned short value)
383 {
384 out << "\\u" << String::toHexString ((int) value).paddedLeft ('0', 4);
385 }
386
387 static void writeString (OutputStream& out, String::CharPointerType t)
388 {
389 for (;;)
390 {
391 auto c = t.getAndAdvance();
392
393 switch (c)
394 {
395 case 0: return;
396
397 case '\"': out << "\\\""; break;
398 case '\\': out << "\\\\"; break;
399 case '\a': out << "\\a"; break;
400 case '\b': out << "\\b"; break;
401 case '\f': out << "\\f"; break;
402 case '\t': out << "\\t"; break;
403 case '\r': out << "\\r"; break;
404 case '\n': out << "\\n"; break;
405
406 default:
407 if (c >= 32 && c < 127)
408 {
409 out << (char) c;
410 }
411 else
412 {
414 {
417 utf16.write (c);
418
419 for (int i = 0; i < 2; ++i)
420 writeEscapedChar (out, (unsigned short) chars[i]);
421 }
422 else
423 {
424 writeEscapedChar (out, (unsigned short) c);
425 }
426 }
427
428 break;
429 }
430 }
431 }
432
433 static void writeSpaces (OutputStream& out, int numSpaces)
434 {
435 out.writeRepeatedByte (' ', (size_t) numSpaces);
436 }
437
438 static void writeArray (OutputStream& out, const Array<var>& array,
440 {
441 out << '[';
442
443 if (! array.isEmpty())
444 {
445 if (! allOnOneLine)
446 out << newLine;
447
448 for (int i = 0; i < array.size(); ++i)
449 {
450 if (! allOnOneLine)
451 writeSpaces (out, indentLevel + indentSize);
452
453 write (out, array.getReference(i), indentLevel + indentSize, allOnOneLine, maximumDecimalPlaces);
454
455 if (i < array.size() - 1)
456 {
457 if (allOnOneLine)
458 out << ", ";
459 else
460 out << ',' << newLine;
461 }
462 else if (! allOnOneLine)
463 out << newLine;
464 }
465
466 if (! allOnOneLine)
467 writeSpaces (out, indentLevel);
468 }
469
470 out << ']';
471 }
472
473 enum { indentSize = 2 };
474};
475
476//==============================================================================
477var JSON::parse (const String& text)
478{
479 var result;
480
481 if (! parse (text, result))
482 result = var();
483
484 return result;
485}
486
488{
489 var result;
490
491 if (! JSONParser::parseAny (text.text, result))
492 result = var();
493
494 return result;
495}
496
498{
499 return parse (input.readEntireStreamAsString());
500}
501
502var JSON::parse (const File& file)
503{
504 return parse (file.loadFileAsString());
505}
506
507Result JSON::parse (const String& text, var& result)
508{
509 return JSONParser::parseObjectOrArray (text.getCharPointer(), result);
510}
511
513{
514 MemoryOutputStream mo (1024);
515 JSONFormatter::write (mo, data, 0, allOnOneLine, maximumDecimalPlaces);
516 return mo.toUTF8();
517}
518
519void JSON::writeToStream (OutputStream& output, const var& data, const bool allOnOneLine, int maximumDecimalPlaces)
520{
521 JSONFormatter::write (output, data, 0, allOnOneLine, maximumDecimalPlaces);
522}
523
525{
527 JSONFormatter::writeString (mo, s.text);
528 return mo.toString();
529}
530
532{
533 auto quote = t.getAndAdvance();
534
535 if (quote == '"' || quote == '\'')
536 return JSONParser::parseString (quote, t, result);
537
538 return Result::fail ("Not a quoted string!");
539}
540
541//==============================================================================
542//==============================================================================
543#if JUCE_UNIT_TESTS
544
545class JSONTests : public UnitTest
546{
547public:
548 JSONTests() : UnitTest ("JSON", "JSON") {}
549
550 static String createRandomWideCharString (Random& r)
551 {
552 juce_wchar buffer[40] = { 0 };
553
554 for (int i = 0; i < numElementsInArray (buffer) - 1; ++i)
555 {
556 if (r.nextBool())
557 {
558 do
559 {
560 buffer[i] = (juce_wchar) (1 + r.nextInt (0x10ffff - 1));
561 }
562 while (! CharPointer_UTF16::canRepresent (buffer[i]));
563 }
564 else
565 buffer[i] = (juce_wchar) (1 + r.nextInt (0xff));
566 }
567
568 return CharPointer_UTF32 (buffer);
569 }
570
571 static String createRandomIdentifier (Random& r)
572 {
573 char buffer[30] = { 0 };
574
575 for (int i = 0; i < numElementsInArray (buffer) - 1; ++i)
576 {
577 static const char chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-:";
578 buffer[i] = chars [r.nextInt (sizeof (chars) - 1)];
579 }
580
581 return CharPointer_ASCII (buffer);
582 }
583
584 // Creates a random double that can be easily stringified, to avoid
585 // false failures when decimal places are rounded or truncated slightly
586 static var createRandomDouble (Random& r)
587 {
588 return var ((r.nextDouble() * 1000.0) + 0.1);
589 }
590
591 static var createRandomVar (Random& r, int depth)
592 {
593 switch (r.nextInt (depth > 3 ? 6 : 8))
594 {
595 case 0: return {};
596 case 1: return r.nextInt();
597 case 2: return r.nextInt64();
598 case 3: return r.nextBool();
599 case 4: return createRandomDouble (r);
600 case 5: return createRandomWideCharString (r);
601
602 case 6:
603 {
604 var v (createRandomVar (r, depth + 1));
605
606 for (int i = 1 + r.nextInt (30); --i >= 0;)
607 v.append (createRandomVar (r, depth + 1));
608
609 return v;
610 }
611
612 case 7:
613 {
614 DynamicObject* o = new DynamicObject();
615
616 for (int i = r.nextInt (30); --i >= 0;)
617 o->setProperty (createRandomIdentifier (r), createRandomVar (r, depth + 1));
618
619 return o;
620 }
621
622 default:
623 return {};
624 }
625 }
626
627 void runTest() override
628 {
629 {
630 beginTest ("JSON");
631
632 Random r = getRandom();
633
634 expect (JSON::parse (String()) == var());
635 expect (JSON::parse ("{}").isObject());
636 expect (JSON::parse ("[]").isArray());
637 expect (JSON::parse ("[ 1234 ]")[0].isInt());
638 expect (JSON::parse ("[ 12345678901234 ]")[0].isInt64());
639 expect (JSON::parse ("[ 1.123e3 ]")[0].isDouble());
640 expect (JSON::parse ("[ -1234]")[0].isInt());
641 expect (JSON::parse ("[-12345678901234]")[0].isInt64());
642 expect (JSON::parse ("[-1.123e3]")[0].isDouble());
643
644 for (int i = 100; --i >= 0;)
645 {
646 var v;
647
648 if (i > 0)
649 v = createRandomVar (r, 0);
650
651 const bool oneLine = r.nextBool();
652 String asString (JSON::toString (v, oneLine));
653 var parsed = JSON::parse ("[" + asString + "]")[0];
655 expect (asString.isNotEmpty() && parsedString == asString);
656 }
657 }
658
659 {
660 beginTest ("Float formatting");
661
662 std::map<double, String> tests;
663 tests[1] = "1.0";
664 tests[1.1] = "1.1";
665 tests[1.01] = "1.01";
666 tests[0.76378] = "0.76378";
667 tests[-10] = "-10.0";
668 tests[10.01] = "10.01";
669 tests[0.0123] = "0.0123";
670 tests[-3.7e-27] = "-3.7e-27";
671 tests[1e+40] = "1.0e40";
672 tests[-12345678901234567.0] = "-1.234567890123457e16";
673 tests[192000] = "192000.0";
674 tests[1234567] = "1.234567e6";
675 tests[0.00006] = "0.00006";
676 tests[0.000006] = "6.0e-6";
677
678 for (auto& test : tests)
679 expectEquals (JSON::toString (test.first), test.second);
680 }
681 }
682};
683
684static JSONTests JSONUnitTests;
685
686#endif
687
688} // 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
Array()=default
Creates an empty array.
void add(const ElementType &newElement)
Appends a new element at the end of the array.
Definition juce_Array.h:375
ElementType & getReference(int index) const noexcept
Returns a direct reference to one of the elements in the array, without checking the index passed in.
Definition juce_Array.h:271
void set(int indexToChange, ParameterType newValue)
Replaces an element with a new value.
Definition juce_Array.h:499
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.
Wraps a pointer to a null-terminated UTF-8 character string, and provides various methods to operate ...
static bool isDigit(char character) noexcept
Checks whether a character is a digit.
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 dynamically implemented object.
Represents a local file or directory.
Definition juce_File.h:45
String loadFileAsString() const
Reads a file into memory as a string.
Represents a string identifier, designed for accessing properties by name.
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.
static Result parse(const String &text, var &parsedResult)
Parses a string of JSON-formatted text, and returns a result code containing any parse errors.
static String escapeString(StringRef)
Returns a version of a string with any extended characters escaped.
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.
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.
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...
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.
bool appendUTF8Char(juce_wchar character)
Appends the utf-8 bytes for a unicode character.
The base class for streams that write data to some kind of destination.
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.
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.
static String toHexString(IntegerType number)
Returns a string representing this numeric value in hexadecimal.
This is a base class for classes that perform a unit test.
A variant class, that can be used to hold a range of primitive values.
Array< var > * getArray() const noexcept
If this variant holds an array, this provides access to it.