28 JSONParser (String::CharPointerType text) : startLocation (text), currentLocation (text) {}
30 String::CharPointerType startLocation, currentLocation;
35 int line = 1, column = 1;
37 String getDescription()
const {
return String (line) +
":" + String (column) +
": error: " + message; }
38 Result getResult()
const {
return Result::fail (getDescription()); }
41 [[noreturn]]
void throwError (juce::String message, String::CharPointerType location)
44 e.message = std::move (message);
46 for (
auto i = startLocation; i < location && ! i.isEmpty(); ++i)
49 if (*i ==
'\n') { e.column = 1; e.line++; }
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; }
61 bool matchString (
const char* t)
70 var parseObjectOrArray()
74 if (matchIf (
'{'))
return parseObject();
75 if (matchIf (
'['))
return parseArray();
78 throwError (
"Expected '{' or '['", currentLocation);
83 String parseString (
const juce_wchar quoteChar)
85 MemoryOutputStream buffer (256);
96 auto errorLocation = currentLocation;
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;
117 for (
int i = 4; --i >= 0;)
122 throwError (
"Syntax error in unicode escape sequence", errorLocation);
124 c = (juce_wchar) ((c << 4) +
static_cast<juce_wchar
> (digitValue));
135 throwError (
"Unexpected EOF in string constant", currentLocation);
137 buffer.appendUTF8Char (c);
140 return buffer.toUTF8();
146 auto originalLocation = currentLocation;
150 case '{':
return parseObject();
151 case '[':
return parseArray();
152 case '"':
return parseString (
'"');
153 case '\'':
return parseString (
'\'');
157 return parseNumber (
true);
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);
165 if (matchString (
"rue"))
171 if (matchString (
"alse"))
177 if (matchString (
"ull"))
186 throwError (
"Syntax error", originalLocation);
189 var parseNumber (
bool isNegative)
191 auto originalPos = currentLocation;
193 int64 intValue = readChar() -
'0';
194 jassert (intValue >= 0 && intValue < 10);
198 auto lastPos = currentLocation;
200 auto digit = ((int) c) -
'0';
202 if (isPositiveAndBelow (digit, 10))
204 intValue = intValue * 10 + digit;
208 if (c ==
'e' || c ==
'E' || c ==
'.')
210 currentLocation = originalPos;
212 return var (isNegative ? -asDouble : asDouble);
216 || c ==
',' || c ==
'}' || c ==
']' || c == 0)
218 currentLocation = lastPos;
222 throwError (
"Syntax error in number", lastPos);
225 auto correctedValue = isNegative ? -intValue : intValue;
227 return (intValue >> 31) != 0 ? var (correctedValue)
228 : var ((int) correctedValue);
233 auto resultObject =
new DynamicObject();
234 var result (resultObject);
235 auto& resultProperties = resultObject->getProperties();
236 auto startOfObjectDecl = currentLocation;
241 auto errorLocation = currentLocation;
248 throwError (
"Unexpected EOF in object declaration", startOfObjectDecl);
251 throwError (
"Expected a property name in double-quotes", errorLocation);
253 errorLocation = currentLocation;
254 Identifier propertyName (parseString (
'"'));
256 if (! propertyName.isValid())
257 throwError (
"Invalid property name", errorLocation);
260 errorLocation = currentLocation;
262 if (readChar() !=
':')
263 throwError (
"Expected ':'", errorLocation);
265 resultProperties.set (propertyName, parseAny());
268 if (matchIf (
','))
continue;
269 if (matchIf (
'}'))
break;
271 throwError (
"Expected ',' or '}'", currentLocation);
279 auto result = var (Array<var>());
280 auto destArray = result.getArray();
281 auto startOfArrayDecl = currentLocation;
291 throwError (
"Unexpected EOF in array declaration", startOfArrayDecl);
293 destArray->add (parseAny());
296 if (matchIf (
','))
continue;
297 if (matchIf (
']'))
break;
299 throwError (
"Expected ',' or ']'", currentLocation);
309 static void writeEscapedChar (OutputStream& out,
const unsigned short value)
314 static void writeString (OutputStream& out, String::CharPointerType t)
318 auto c = t.getAndAdvance();
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;
334 if (c >= 32 && c < 127)
342 CharPointer_UTF16::CharType chars[2];
343 CharPointer_UTF16 utf16 (chars);
346 for (
int i = 0; i < 2; ++i)
347 writeEscapedChar (out, (
unsigned short) chars[i]);
351 writeEscapedChar (out, (
unsigned short) c);
360 static void writeSpaces (OutputStream& out,
int numSpaces)
362 out.writeRepeatedByte (
' ', (
size_t) numSpaces);
365 static void writeArray (OutputStream& out,
const Array<var>& array,
const JSON::FormatOptions& format)
369 if (! array.isEmpty())
374 for (
int i = 0; i < array.size(); ++i)
377 writeSpaces (out, format.getIndentLevel() + indentSize);
379 JSON::writeToStream (out, array.getReference (i), format.withIndentLevel (format.getIndentLevel() + indentSize));
381 if (i < array.size() - 1)
385 switch (format.getSpacing())
397 writeSpaces (out, format.getIndentLevel());
403 enum { indentSize = 2 };
419 else if (v.isUndefined())
425 out << (static_cast<bool> (v) ?
"true" :
"false");
427 else if (v.isDouble())
429 auto d =
static_cast<double> (v);
431 if (juce_isfinite (d))
433 out << serialiseDouble (d);
440 else if (v.isArray())
442 JSONFormatter::writeArray (out, *v.
getArray(), opt);
444 else if (v.isObject())
446 if (
auto*
object = v.getDynamicObject())
447 object->writeAsJSON (out, opt);
454 jassert (! (v.isMethod() || v.isBinaryData()));
472 if (
parse (text, result))
482 return JSONParser (text.
text).parseAny();
484 catch (
const JSONParser::ErrorException&) {}
505 catch (
const JSONParser::ErrorException& error)
507 return error.getResult();
516 .withMaxDecimalPlaces (maximumDecimalPlaces));
522 .withMaxDecimalPlaces (maximumDecimalPlaces));
528 JSONFormatter::writeString (mo, s.
text);
536 JSONParser parser (t);
537 auto quote = parser.readChar();
539 if (quote !=
'"' && quote !=
'\'')
542 result = parser.parseString (quote);
543 t = parser.currentLocation;
545 catch (
const JSONParser::ErrorException& error)
547 return error.getResult();
558class JSONTests final :
public UnitTest
562 :
UnitTest (
"JSON", UnitTestCategories::json)
565 static String createRandomWideCharString (Random& r)
567 juce_wchar buffer[40] = { 0 };
569 for (
int i = 0; i < numElementsInArray (buffer) - 1; ++i)
575 buffer[i] = (juce_wchar) (1 + r.nextInt (0x10ffff - 1));
577 while (! CharPointer_UTF16::canRepresent (buffer[i]));
580 buffer[i] = (juce_wchar) (1 + r.nextInt (0xff));
583 return CharPointer_UTF32 (buffer);
586 static String createRandomIdentifier (Random& r)
588 char buffer[30] = { 0 };
590 for (
int i = 0; i < numElementsInArray (buffer) - 1; ++i)
592 static const char chars[] =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-:";
593 buffer[i] = chars [r.nextInt (
sizeof (chars) - 1)];
596 return CharPointer_ASCII (buffer);
601 static var createRandomDouble (Random& r)
603 return var ((r.nextDouble() * 1000.0) + 0.1);
606 static var createRandomVar (Random& r,
int depth)
608 switch (r.nextInt (depth > 3 ? 6 : 8))
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);
619 var v (createRandomVar (r, depth + 1));
621 for (
int i = 1 + r.nextInt (30); --i >= 0;)
622 v.append (createRandomVar (r, depth + 1));
629 auto o =
new DynamicObject();
631 for (
int i = r.nextInt (30); --i >= 0;)
632 o->setProperty (createRandomIdentifier (r), createRandomVar (r, depth + 1));
642 void runTest()
override
647 auto r = getRandom();
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());
659 for (
int i = 100; --i >= 0;)
664 v = createRandomVar (r, 0);
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);
675 beginTest (
"Float formatting");
677 std::map<double, String> tests;
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";
693 for (
auto& test : tests)
694 expectEquals (JSON::toString (test.first), test.second);
699static JSONTests JSONUnitTests;
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
static var fromString(StringRef)
static Result parse(const String &text, var &parsedResult)
static String escapeString(StringRef)
@ none
All optional whitespace should be omitted.
@ multiLine
Newlines and spaces will be included in the output, in order to make it easy to read for humans.
@ singleLine
All output should be on a single line, but with some additional spacing, e.g. after commas and colons...
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
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