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