OpenShot Audio Library | OpenShotAudio 0.4.0
 
Loading...
Searching...
No Matches
juce_JSON.cpp
1/*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
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 default: break;
131 }
132 }
133
134 if (c == 0)
135 throwError ("Unexpected EOF in string constant", currentLocation);
136
137 buffer.appendUTF8Char (c);
138 }
139
140 return buffer.toUTF8();
141 }
142
143 var parseAny()
144 {
145 skipWhitespace();
146 auto originalLocation = currentLocation;
147
148 switch (readChar())
149 {
150 case '{': return parseObject();
151 case '[': return parseArray();
152 case '"': return parseString ('"');
153 case '\'': return parseString ('\'');
154
155 case '-':
156 skipWhitespace();
157 return parseNumber (true);
158
159 case '0': case '1': case '2': case '3': case '4':
160 case '5': case '6': case '7': case '8': case '9':
161 currentLocation = originalLocation;
162 return parseNumber (false);
163
164 case 't': // "true"
165 if (matchString ("rue"))
166 return var (true);
167
168 break;
169
170 case 'f': // "false"
171 if (matchString ("alse"))
172 return var (false);
173
174 break;
175
176 case 'n': // "null"
177 if (matchString ("ull"))
178 return {};
179
180 break;
181
182 default:
183 break;
184 }
185
186 throwError ("Syntax error", originalLocation);
187 }
188
189 var parseNumber (bool isNegative)
190 {
191 auto originalPos = currentLocation;
192
193 int64 intValue = readChar() - '0';
194 jassert (intValue >= 0 && intValue < 10);
195
196 for (;;)
197 {
198 auto lastPos = currentLocation;
199 auto c = readChar();
200 auto digit = ((int) c) - '0';
201
202 if (isPositiveAndBelow (digit, 10))
203 {
204 intValue = intValue * 10 + digit;
205 continue;
206 }
207
208 if (c == 'e' || c == 'E' || c == '.')
209 {
210 currentLocation = originalPos;
211 auto asDouble = CharacterFunctions::readDoubleValue (currentLocation);
212 return var (isNegative ? -asDouble : asDouble);
213 }
214
216 || c == ',' || c == '}' || c == ']' || c == 0)
217 {
218 currentLocation = lastPos;
219 break;
220 }
221
222 throwError ("Syntax error in number", lastPos);
223 }
224
225 auto correctedValue = isNegative ? -intValue : intValue;
226
227 return (intValue >> 31) != 0 ? var (correctedValue)
228 : var ((int) correctedValue);
229 }
230
231 var parseObject()
232 {
233 auto resultObject = new DynamicObject();
234 var result (resultObject);
235 auto& resultProperties = resultObject->getProperties();
236 auto startOfObjectDecl = currentLocation;
237
238 for (;;)
239 {
240 skipWhitespace();
241 auto errorLocation = currentLocation;
242 auto c = readChar();
243
244 if (c == '}')
245 break;
246
247 if (c == 0)
248 throwError ("Unexpected EOF in object declaration", startOfObjectDecl);
249
250 if (c != '"')
251 throwError ("Expected a property name in double-quotes", errorLocation);
252
253 errorLocation = currentLocation;
254 Identifier propertyName (parseString ('"'));
255
256 if (! propertyName.isValid())
257 throwError ("Invalid property name", errorLocation);
258
259 skipWhitespace();
260 errorLocation = currentLocation;
261
262 if (readChar() != ':')
263 throwError ("Expected ':'", errorLocation);
264
265 resultProperties.set (propertyName, parseAny());
266
267 skipWhitespace();
268 if (matchIf (',')) continue;
269 if (matchIf ('}')) break;
270
271 throwError ("Expected ',' or '}'", currentLocation);
272 }
273
274 return result;
275 }
276
277 var parseArray()
278 {
279 auto result = var (Array<var>());
280 auto destArray = result.getArray();
281 auto startOfArrayDecl = currentLocation;
282
283 for (;;)
284 {
285 skipWhitespace();
286
287 if (matchIf (']'))
288 break;
289
290 if (isEOF())
291 throwError ("Unexpected EOF in array declaration", startOfArrayDecl);
292
293 destArray->add (parseAny());
294 skipWhitespace();
295
296 if (matchIf (',')) continue;
297 if (matchIf (']')) break;
298
299 throwError ("Expected ',' or ']'", currentLocation);
300 }
301
302 return result;
303 }
304};
305
306//==============================================================================
307struct JSONFormatter
308{
309 static void writeEscapedChar (OutputStream& out, const unsigned short value)
310 {
311 out << "\\u" << String::toHexString ((int) value).paddedLeft ('0', 4);
312 }
313
314 static void writeString (OutputStream& out, String::CharPointerType t)
315 {
316 for (;;)
317 {
318 auto c = t.getAndAdvance();
319
320 switch (c)
321 {
322 case 0: return;
323
324 case '\"': out << "\\\""; break;
325 case '\\': out << "\\\\"; break;
326 case '\a': out << "\\a"; break;
327 case '\b': out << "\\b"; break;
328 case '\f': out << "\\f"; break;
329 case '\t': out << "\\t"; break;
330 case '\r': out << "\\r"; break;
331 case '\n': out << "\\n"; break;
332
333 default:
334 if (c >= 32 && c < 127)
335 {
336 out << (char) c;
337 }
338 else
339 {
341 {
342 CharPointer_UTF16::CharType chars[2];
343 CharPointer_UTF16 utf16 (chars);
344 utf16.write (c);
345
346 for (int i = 0; i < 2; ++i)
347 writeEscapedChar (out, (unsigned short) chars[i]);
348 }
349 else
350 {
351 writeEscapedChar (out, (unsigned short) c);
352 }
353 }
354
355 break;
356 }
357 }
358 }
359
360 static void writeSpaces (OutputStream& out, int numSpaces)
361 {
362 out.writeRepeatedByte (' ', (size_t) numSpaces);
363 }
364
365 static void writeArray (OutputStream& out, const Array<var>& array, const JSON::FormatOptions& format)
366 {
367 out << '[';
368
369 if (! array.isEmpty())
370 {
371 if (format.getSpacing() == JSON::Spacing::multiLine)
372 out << newLine;
373
374 for (int i = 0; i < array.size(); ++i)
375 {
376 if (format.getSpacing() == JSON::Spacing::multiLine)
377 writeSpaces (out, format.getIndentLevel() + indentSize);
378
379 JSON::writeToStream (out, array.getReference (i), format.withIndentLevel (format.getIndentLevel() + indentSize));
380
381 if (i < array.size() - 1)
382 {
383 out << ",";
384
385 switch (format.getSpacing())
386 {
387 case JSON::Spacing::none: break;
388 case JSON::Spacing::singleLine: out << ' '; break;
389 case JSON::Spacing::multiLine: out << newLine; break;
390 }
391 }
392 else if (format.getSpacing() == JSON::Spacing::multiLine)
393 out << newLine;
394 }
395
396 if (format.getSpacing() == JSON::Spacing::multiLine)
397 writeSpaces (out, format.getIndentLevel());
398 }
399
400 out << ']';
401 }
402
403 enum { indentSize = 2 };
404};
405
406
407void JSON::writeToStream (OutputStream& out, const var& v, const FormatOptions& opt)
408{
409 if (v.isString())
410 {
411 out << '"';
412 JSONFormatter::writeString (out, v.toString().getCharPointer());
413 out << '"';
414 }
415 else if (v.isVoid())
416 {
417 out << "null";
418 }
419 else if (v.isUndefined())
420 {
421 out << "undefined";
422 }
423 else if (v.isBool())
424 {
425 out << (static_cast<bool> (v) ? "true" : "false");
426 }
427 else if (v.isDouble())
428 {
429 auto d = static_cast<double> (v);
430
431 if (juce_isfinite (d))
432 {
433 out << serialiseDouble (d);
434 }
435 else
436 {
437 out << "null";
438 }
439 }
440 else if (v.isArray())
441 {
442 JSONFormatter::writeArray (out, *v.getArray(), opt);
443 }
444 else if (v.isObject())
445 {
446 if (auto* object = v.getDynamicObject())
447 object->writeAsJSON (out, opt);
448 else
449 jassertfalse; // Only DynamicObjects can be converted to JSON!
450 }
451 else
452 {
453 // Can't convert these other types of object to JSON!
454 jassert (! (v.isMethod() || v.isBinaryData()));
455
456 out << v.toString();
457 }
458}
459
461{
462 MemoryOutputStream mo { 1024 };
463 writeToStream (mo, v, opt);
464 return mo.toUTF8();
465}
466
467//==============================================================================
468var JSON::parse (const String& text)
469{
470 var result;
471
472 if (parse (text, result))
473 return result;
474
475 return {};
476}
477
479{
480 try
481 {
482 return JSONParser (text.text).parseAny();
483 }
484 catch (const JSONParser::ErrorException&) {}
485
486 return {};
487}
488
490{
491 return parse (input.readEntireStreamAsString());
492}
493
494var JSON::parse (const File& file)
495{
496 return parse (file.loadFileAsString());
497}
498
499Result JSON::parse (const String& text, var& result)
500{
501 try
502 {
503 result = JSONParser (text.getCharPointer()).parseObjectOrArray();
504 }
505 catch (const JSONParser::ErrorException& error)
506 {
507 return error.getResult();
508 }
509
510 return Result::ok();
511}
512
513String JSON::toString (const var& data, const bool allOnOneLine, int maximumDecimalPlaces)
514{
516 .withMaxDecimalPlaces (maximumDecimalPlaces));
517}
518
519void JSON::writeToStream (OutputStream& output, const var& data, const bool allOnOneLine, int maximumDecimalPlaces)
520{
522 .withMaxDecimalPlaces (maximumDecimalPlaces));
523}
524
526{
528 JSONFormatter::writeString (mo, s.text);
529 return mo.toString();
530}
531
532Result JSON::parseQuotedString (String::CharPointerType& t, var& result)
533{
534 try
535 {
536 JSONParser parser (t);
537 auto quote = parser.readChar();
538
539 if (quote != '"' && quote != '\'')
540 return Result::fail ("Not a quoted string!");
541
542 result = parser.parseString (quote);
543 t = parser.currentLocation;
544 }
545 catch (const JSONParser::ErrorException& error)
546 {
547 return error.getResult();
548 }
549
550 return Result::ok();
551}
552
553
554//==============================================================================
555//==============================================================================
556#if JUCE_UNIT_TESTS
557
558class JSONTests final : public UnitTest
559{
560public:
561 JSONTests()
562 : UnitTest ("JSON", UnitTestCategories::json)
563 {}
564
565 static String createRandomWideCharString (Random& r)
566 {
567 juce_wchar buffer[40] = { 0 };
568
569 for (int i = 0; i < numElementsInArray (buffer) - 1; ++i)
570 {
571 if (r.nextBool())
572 {
573 do
574 {
575 buffer[i] = (juce_wchar) (1 + r.nextInt (0x10ffff - 1));
576 }
577 while (! CharPointer_UTF16::canRepresent (buffer[i]));
578 }
579 else
580 buffer[i] = (juce_wchar) (1 + r.nextInt (0xff));
581 }
582
583 return CharPointer_UTF32 (buffer);
584 }
585
586 static String createRandomIdentifier (Random& r)
587 {
588 char buffer[30] = { 0 };
589
590 for (int i = 0; i < numElementsInArray (buffer) - 1; ++i)
591 {
592 static const char chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-:";
593 buffer[i] = chars [r.nextInt (sizeof (chars) - 1)];
594 }
595
596 return CharPointer_ASCII (buffer);
597 }
598
599 // Creates a random double that can be easily stringified, to avoid
600 // false failures when decimal places are rounded or truncated slightly
601 static var createRandomDouble (Random& r)
602 {
603 return var ((r.nextDouble() * 1000.0) + 0.1);
604 }
605
606 static var createRandomVar (Random& r, int depth)
607 {
608 switch (r.nextInt (depth > 3 ? 6 : 8))
609 {
610 case 0: return {};
611 case 1: return r.nextInt();
612 case 2: return r.nextInt64();
613 case 3: return r.nextBool();
614 case 4: return createRandomDouble (r);
615 case 5: return createRandomWideCharString (r);
616
617 case 6:
618 {
619 var v (createRandomVar (r, depth + 1));
620
621 for (int i = 1 + r.nextInt (30); --i >= 0;)
622 v.append (createRandomVar (r, depth + 1));
623
624 return v;
625 }
626
627 case 7:
628 {
629 auto o = new DynamicObject();
630
631 for (int i = r.nextInt (30); --i >= 0;)
632 o->setProperty (createRandomIdentifier (r), createRandomVar (r, depth + 1));
633
634 return o;
635 }
636
637 default:
638 return {};
639 }
640 }
641
642 void runTest() override
643 {
644 {
645 beginTest ("JSON");
646
647 auto r = getRandom();
648
649 expect (JSON::parse (String()) == var());
650 expect (JSON::parse ("{}").isObject());
651 expect (JSON::parse ("[]").isArray());
652 expect (JSON::parse ("[ 1234 ]")[0].isInt());
653 expect (JSON::parse ("[ 12345678901234 ]")[0].isInt64());
654 expect (JSON::parse ("[ 1.123e3 ]")[0].isDouble());
655 expect (JSON::parse ("[ -1234]")[0].isInt());
656 expect (JSON::parse ("[-12345678901234]")[0].isInt64());
657 expect (JSON::parse ("[-1.123e3]")[0].isDouble());
658
659 for (int i = 100; --i >= 0;)
660 {
661 var v;
662
663 if (i > 0)
664 v = createRandomVar (r, 0);
665
666 const auto oneLine = r.nextBool();
667 const auto asString = JSON::toString (v, oneLine);
668 const auto parsed = JSON::parse ("[" + asString + "]")[0];
669 const auto parsedString = JSON::toString (parsed, oneLine);
670 expect (asString.isNotEmpty() && parsedString == asString);
671 }
672 }
673
674 {
675 beginTest ("Float formatting");
676
677 std::map<double, String> tests;
678 tests[1] = "1.0";
679 tests[1.1] = "1.1";
680 tests[1.01] = "1.01";
681 tests[0.76378] = "0.76378";
682 tests[-10] = "-10.0";
683 tests[10.01] = "10.01";
684 tests[0.0123] = "0.0123";
685 tests[-3.7e-27] = "-3.7e-27";
686 tests[1e+40] = "1.0e40";
687 tests[-12345678901234567.0] = "-1.234567890123457e16";
688 tests[192000] = "192000.0";
689 tests[1234567] = "1.234567e6";
690 tests[0.00006] = "0.00006";
691 tests[0.000006] = "6.0e-6";
692
693 for (auto& test : tests)
694 expectEquals (JSON::toString (test.first), test.second);
695 }
696 }
697};
698
699static JSONTests JSONUnitTests;
700
701#endif
702
703} // namespace juce
static size_t getBytesRequiredFor(juce_wchar charToWrite) noexcept
static bool isWhitespace(char character) noexcept
static double readDoubleValue(CharPointerType &text) noexcept
static int getHexDigitValue(juce_wchar digit) noexcept
String loadFileAsString() const
virtual String readEntireStreamAsString()
FormatOptions withSpacing(Spacing x) const
Definition juce_JSON.h:105
static var fromString(StringRef)
static Result parse(const String &text, var &parsedResult)
static String escapeString(StringRef)
@ none
All optional whitespace should be omitted.
Definition juce_JSON.h:93
@ multiLine
Newlines and spaces will be included in the output, in order to make it easy to read for humans.
Definition juce_JSON.h:95
@ singleLine
All output should be on a single line, but with some additional spacing, e.g. after commas and colons...
Definition juce_JSON.h:94
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
String paddedLeft(juce_wchar padCharacter, int minimumLength) const
static String toHexString(IntegerType number)
Array< var > * getArray() const noexcept