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 {
JSONParserjuce::JSONParser28 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
getDescriptionjuce::JSONParser::ErrorException37 String getDescription() const { return String (line) + ":" + String (column) + ": error: " + message; }
getResultjuce::JSONParser::ErrorException38 Result getResult() const { return Result::fail (getDescription()); }
39 };
40
throwErrorjuce::JSONParser41 [[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
skipWhitespacejuce::JSONParser55 void skipWhitespace() { currentLocation = currentLocation.findEndOfWhitespace(); }
readCharjuce::JSONParser56 juce_wchar readChar() { return currentLocation.getAndAdvance(); }
peekCharjuce::JSONParser57 juce_wchar peekChar() const { return *currentLocation; }
matchIfjuce::JSONParser58 bool matchIf (char c) { if (peekChar() == (juce_wchar) c) { ++currentLocation; return true; } return false; }
isEOFjuce::JSONParser59 bool isEOF() const { return peekChar() == 0; }
60
matchStringjuce::JSONParser61 bool matchString (const char* t)
62 {
63 while (*t != 0)
64 if (! matchIf (*t++))
65 return false;
66
67 return true;
68 }
69
parseObjectOrArrayjuce::JSONParser70 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
parseStringjuce::JSONParser83 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
parseAnyjuce::JSONParser141 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
parseNumberjuce::JSONParser187 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
213 if (CharacterFunctions::isWhitespace (c)
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
parseObjectjuce::JSONParser229 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
parseArrayjuce::JSONParser275 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 {
writejuce::JSONFormatter307 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
writeEscapedCharjuce::JSONFormatter361 static void writeEscapedChar (OutputStream& out, const unsigned short value)
362 {
363 out << "\\u" << String::toHexString ((int) value).paddedLeft ('0', 4);
364 }
365
writeStringjuce::JSONFormatter366 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 {
392 if (CharPointer_UTF16::getBytesRequiredFor (c) > 2)
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
writeSpacesjuce::JSONFormatter412 static void writeSpaces (OutputStream& out, int numSpaces)
413 {
414 out.writeRepeatedByte (' ', (size_t) numSpaces);
415 }
416
writeArrayjuce::JSONFormatter417 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 //==============================================================================
parse(const String & text)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
fromString(StringRef text)466 var JSON::fromString (StringRef text)
467 {
468 try
469 {
470 return JSONParser (text.text).parseAny();
471 }
472 catch (const JSONParser::ErrorException&) {}
473
474 return {};
475 }
476
parse(InputStream & input)477 var JSON::parse (InputStream& input)
478 {
479 return parse (input.readEntireStreamAsString());
480 }
481
parse(const File & file)482 var JSON::parse (const File& file)
483 {
484 return parse (file.loadFileAsString());
485 }
486
parse(const String & text,var & result)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
toString(const var & data,const bool allOnOneLine,int maximumDecimalPlaces)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
writeToStream(OutputStream & output,const var & data,const bool allOnOneLine,int maximumDecimalPlaces)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
escapeString(StringRef s)513 String JSON::escapeString (StringRef s)
514 {
515 MemoryOutputStream mo;
516 JSONFormatter::writeString (mo, s.text);
517 return mo.toString();
518 }
519
parseQuotedString(String::CharPointerType & t,var & result)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:
JSONTests()549 JSONTests()
550 : UnitTest ("JSON", UnitTestCategories::json)
551 {}
552
createRandomWideCharString(Random & r)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
createRandomIdentifier(Random & r)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
createRandomDouble(Random & r)589 static var createRandomDouble (Random& r)
590 {
591 return var ((r.nextDouble() * 1000.0) + 0.1);
592 }
593
createRandomVar(Random & r,int depth)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
runTest()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
692