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));
133 throwError (
"Unexpected EOF in string constant", currentLocation);
135 buffer.appendUTF8Char (c);
138 return buffer.toUTF8();
144 auto originalLocation = currentLocation;
148 case '{':
return parseObject();
149 case '[':
return parseArray();
150 case '"':
return parseString (
'"');
151 case '\'':
return parseString (
'\'');
155 return parseNumber (
true);
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);
163 if (matchString (
"rue"))
169 if (matchString (
"alse"))
175 if (matchString (
"ull"))
184 throwError (
"Syntax error", originalLocation);
187 var parseNumber (
bool isNegative)
189 auto originalPos = currentLocation;
191 int64 intValue = readChar() -
'0';
192 jassert (intValue >= 0 && intValue < 10);
196 auto lastPos = currentLocation;
198 auto digit = ((int) c) -
'0';
200 if (isPositiveAndBelow (digit, 10))
202 intValue = intValue * 10 + digit;
206 if (c ==
'e' || c ==
'E' || c ==
'.')
208 currentLocation = originalPos;
210 return var (isNegative ? -asDouble : asDouble);
214 || c ==
',' || c ==
'}' || c ==
']' || c == 0)
216 currentLocation = lastPos;
220 throwError (
"Syntax error in number", lastPos);
223 auto correctedValue = isNegative ? -intValue : intValue;
225 return (intValue >> 31) != 0 ? var (correctedValue)
226 : var ((int) correctedValue);
231 auto resultObject =
new DynamicObject();
232 var result (resultObject);
233 auto& resultProperties = resultObject->getProperties();
234 auto startOfObjectDecl = currentLocation;
239 auto errorLocation = currentLocation;
246 throwError (
"Unexpected EOF in object declaration", startOfObjectDecl);
249 throwError (
"Expected a property name in double-quotes", errorLocation);
251 errorLocation = currentLocation;
252 Identifier propertyName (parseString (
'"'));
254 if (! propertyName.isValid())
255 throwError (
"Invalid property name", errorLocation);
258 errorLocation = currentLocation;
260 if (readChar() !=
':')
261 throwError (
"Expected ':'", errorLocation);
263 resultProperties.set (propertyName, parseAny());
266 if (matchIf (
','))
continue;
267 if (matchIf (
'}'))
break;
269 throwError (
"Expected ',' or '}'", currentLocation);
277 auto result = var (Array<var>());
278 auto destArray = result.getArray();
279 auto startOfArrayDecl = currentLocation;
289 throwError (
"Unexpected EOF in array declaration", startOfArrayDecl);
291 destArray->add (parseAny());
294 if (matchIf (
','))
continue;
295 if (matchIf (
']'))
break;
297 throwError (
"Expected ',' or ']'", currentLocation);
307 static void write (OutputStream& out,
const var& v,
308 int indentLevel,
bool allOnOneLine,
int maximumDecimalPlaces)
313 writeString (out, v.toString().getCharPointer());
320 else if (v.isUndefined())
326 out << (static_cast<bool> (v) ?
"true" :
"false");
328 else if (v.isDouble())
330 auto d =
static_cast<double> (v);
332 if (juce_isfinite (d))
334 out << serialiseDouble (d);
341 else if (v.isArray())
343 writeArray (out, *v.getArray(), indentLevel, allOnOneLine, maximumDecimalPlaces);
345 else if (v.isObject())
347 if (
auto*
object = v.getDynamicObject())
348 object->writeAsJSON (out, indentLevel, allOnOneLine, maximumDecimalPlaces);
355 jassert (! (v.isMethod() || v.isBinaryData()));
361 static void writeEscapedChar (OutputStream& out,
const unsigned short value)
366 static void writeString (OutputStream& out, String::CharPointerType t)
370 auto c = t.getAndAdvance();
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;
386 if (c >= 32 && c < 127)
394 CharPointer_UTF16::CharType chars[2];
395 CharPointer_UTF16 utf16 (chars);
398 for (
int i = 0; i < 2; ++i)
399 writeEscapedChar (out, (
unsigned short) chars[i]);
403 writeEscapedChar (out, (
unsigned short) c);
412 static void writeSpaces (OutputStream& out,
int numSpaces)
414 out.writeRepeatedByte (
' ', (
size_t) numSpaces);
417 static void writeArray (OutputStream& out,
const Array<var>& array,
418 int indentLevel,
bool allOnOneLine,
int maximumDecimalPlaces)
422 if (! array.isEmpty())
427 for (
int i = 0; i < array.size(); ++i)
430 writeSpaces (out, indentLevel + indentSize);
432 write (out, array.getReference(i), indentLevel + indentSize, allOnOneLine, maximumDecimalPlaces);
434 if (i < array.size() - 1)
439 out <<
',' << newLine;
441 else if (! allOnOneLine)
446 writeSpaces (out, indentLevel);
452 enum { indentSize = 2 };
460 if (
parse (text, result))
470 return JSONParser (text.
text).parseAny();
472 catch (
const JSONParser::ErrorException&) {}
493 catch (
const JSONParser::ErrorException& error)
495 return error.getResult();
516 JSONFormatter::writeString (
mo, s.
text);
517 return mo.toString();
533 catch (
const JSONParser::ErrorException& error)
535 return error.getResult();
550 :
UnitTest (
"JSON", UnitTestCategories::json)
555 juce_wchar buffer[40] = { 0 };
557 for (
int i = 0; i < numElementsInArray (buffer) - 1; ++i)
563 buffer[i] = (juce_wchar) (1 + r.nextInt (0x10ffff - 1));
568 buffer[i] = (juce_wchar) (1 + r.nextInt (0xff));
571 return CharPointer_UTF32 (buffer);
576 char buffer[30] = { 0 };
578 for (
int i = 0; i < numElementsInArray (buffer) - 1; ++i)
580 static const char chars[] =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-:";
581 buffer[i] =
chars [r.nextInt (
sizeof (
chars) - 1)];
584 return CharPointer_ASCII (buffer);
591 return var ((r.nextDouble() * 1000.0) + 0.1);
596 switch (r.nextInt (
depth > 3 ? 6 : 8))
599 case 1:
return r.nextInt();
600 case 2:
return r.nextInt64();
601 case 3:
return r.nextBool();
609 for (
int i = 1 + r.nextInt (30); --i >= 0;)
617 auto o =
new DynamicObject();
619 for (
int i = r.nextInt (30); --i >= 0;)
630 void runTest()
override
635 auto r = getRandom();
641 expect (
JSON::parse (
"[ 12345678901234 ]")[0].isInt64());
642 expect (
JSON::parse (
"[ 1.123e3 ]")[0].isDouble());
644 expect (
JSON::parse (
"[-12345678901234]")[0].isInt64());
647 for (
int i = 100; --i >= 0;)
654 const bool oneLine = r.nextBool();
663 beginTest (
"Float formatting");
665 std::map<double, String>
tests;
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";
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";
687static JSONTests JSONUnitTests;
static size_t getBytesRequiredFor(juce_wchar charToWrite) noexcept
static bool canRepresent(juce_wchar character) noexcept
static double readDoubleValue(CharPointerType &text) noexcept
static int getHexDigitValue(juce_wchar digit) noexcept
static bool isWhitespace(char character) noexcept
String loadFileAsString() const
static var fromString(StringRef)
static Result parse(const String &text, var &parsedResult)
static String escapeString(StringRef)
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
static String toHexString(IntegerType number)