35 int line = 1, column = 1;
37 String getDescription()
const {
return String (line) +
":" +
String (column) +
": error: " + message; }
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++; }
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);
308 int indentLevel,
bool allOnOneLine,
int maximumDecimalPlaces)
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)
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];
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)
418 int indentLevel,
bool allOnOneLine,
int maximumDecimalPlaces)
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))
495 return error.getResult();
504 JSONFormatter::write (mo, data, 0, allOnOneLine, maximumDecimalPlaces);
510 JSONFormatter::write (output, data, 0, allOnOneLine, maximumDecimalPlaces);
516 JSONFormatter::writeString (mo, s.
text);
525 auto quote = parser.readChar();
527 if (quote !=
'"' && quote !=
'\'')
530 result = parser.parseString (quote);
531 t = parser.currentLocation;
535 return error.getResult();
550 :
UnitTest (
"JSON", UnitTestCategories::json)
553 static String createRandomWideCharString (Random& r)
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);
574 static String createRandomIdentifier (Random& r)
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);
589 static var createRandomDouble (Random& r)
591 return var ((r.nextDouble() * 1000.0) + 0.1);
594 static var createRandomVar (Random& r,
int depth)
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();
602 case 4:
return createRandomDouble (r);
603 case 5:
return createRandomWideCharString (r);
607 var v (createRandomVar (r, depth + 1));
609 for (
int i = 1 + r.nextInt (30); --i >= 0;)
610 v.append (createRandomVar (r, depth + 1));
617 auto o =
new DynamicObject();
619 for (
int i = r.nextInt (30); --i >= 0;)
620 o->setProperty (createRandomIdentifier (r), createRandomVar (r, depth + 1));
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;)
652 v = createRandomVar (r, 0);
654 const bool oneLine = r.nextBool();
656 var parsed =
JSON::parse (
"[" + asString +
"]")[0];
658 expect (asString.isNotEmpty() && parsedString == asString);
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";
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";
681 for (
auto& test : tests)
687static JSONTests JSONUnitTests;
Holds a resizable array of primitive or copy-by-value objects.
bool isEmpty() const noexcept
Returns true if the array is empty, false otherwise.
int size() const noexcept
Returns the current number of elements in the array.
ElementType & getReference(int index) noexcept
Returns a direct reference to one of the elements in the array, without checking the index passed in.
Wraps a pointer to a null-terminated UTF-16 character string, and provides various methods to operate...
static size_t getBytesRequiredFor(juce_wchar charToWrite) noexcept
Returns the number of bytes that would be needed to represent the given unicode character in this enc...
static bool canRepresent(juce_wchar character) noexcept
Returns true if the given unicode character can be represented in this encoding.
void write(juce_wchar charToWrite) noexcept
Writes a unicode character to this string, and advances this pointer to point to the next position.
Wraps a pointer to a null-terminated UTF-8 character string, and provides various methods to operate ...
juce_wchar getAndAdvance() noexcept
Returns the character that this pointer is currently pointing to, and then advances the pointer to po...
bool isEmpty() const noexcept
Returns true if this pointer is pointing to a null character.
CharPointer_UTF8 findEndOfWhitespace() const noexcept
Returns the first non-whitespace character in the string.
static double readDoubleValue(CharPointerType &text) noexcept
Parses a character string to read a floating-point number.
static int getHexDigitValue(juce_wchar digit) noexcept
Returns 0 to 16 for '0' to 'F", or -1 for characters that aren't a legal hex digit.
static bool isWhitespace(char character) noexcept
Checks whether a character is whitespace.
Represents a local file or directory.
String loadFileAsString() const
Reads a file into memory as a string.
static var fromString(StringRef)
Parses a string that was created with the toString() method.
static Result parse(const String &text, var &parsedResult)
Parses a string of JSON-formatted text, and returns a result code containing any parse errors.
static String escapeString(StringRef)
Returns a version of a string with any extended characters escaped.
static String toString(const var &objectToFormat, bool allOnOneLine=false, int maximumDecimalPlaces=15)
Returns a string which contains a JSON-formatted representation of the var object.
static void writeToStream(OutputStream &output, const var &objectToFormat, bool allOnOneLine=false, int maximumDecimalPlaces=15)
Writes a JSON-formatted representation of the var object to the given stream.
static Result parseQuotedString(String::CharPointerType &text, var &result)
Parses a quoted string-literal in JSON format, returning the un-escaped result in the result paramete...
Writes data to an internal memory buffer, which grows as required.
String toUTF8() const
Returns a String created from the (UTF8) data that has been written to the stream.
String toString() const
Attempts to detect the encoding of the data and convert it to a string.
The base class for streams that write data to some kind of destination.
virtual bool writeRepeatedByte(uint8 byte, size_t numTimesToRepeat)
Writes a byte to the output stream a given number of times.
Represents the 'success' or 'failure' of an operation, and holds an associated error message to descr...
static Result fail(const String &errorMessage) noexcept
Creates a 'failure' result.
static Result ok() noexcept
Creates and returns a 'successful' result.
A simple class for holding temporary references to a string literal or String.
String::CharPointerType text
The text that is referenced.
CharPointerType getCharPointer() const noexcept
Returns the character pointer currently being used to store this string.
String paddedLeft(juce_wchar padCharacter, int minimumLength) const
Returns a copy of this string with the specified character repeatedly added to its beginning until th...
static String toHexString(IntegerType number)
Returns a string representing this numeric value in hexadecimal.
This is a base class for classes that perform a unit test.
A variant class, that can be used to hold a range of primitive values.
Array< var > * getArray() const noexcept
If this variant holds an array, this provides access to it.