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++; }
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);
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';
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)
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];
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)
374 for (
int i = 0; i < array.
size(); ++i)
377 writeSpaces (out, 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);
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))
507 return error.getResult();
516 .withMaxDecimalPlaces (maximumDecimalPlaces));
522 .withMaxDecimalPlaces (maximumDecimalPlaces));
528 JSONFormatter::writeString (mo, s.
text);
537 auto quote = parser.readChar();
539 if (quote !=
'"' && quote !=
'\'')
542 result = parser.parseString (quote);
543 t = parser.currentLocation;
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 };
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 };
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");
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;
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...
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 bool isWhitespace(char character) noexcept
Checks whether a character is whitespace.
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.
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.
@ 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)
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 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.
NewLine newLine
A predefined object representing a new-line, which can be written to a string or stream.
wchar_t juce_wchar
A platform-independent 32-bit unicode character type.
bool juce_isfinite(NumericType value) noexcept
The isfinite() method seems to vary between platforms, so this is a platform-independent function for...
bool isPositiveAndBelow(Type1 valueToTest, Type2 upperLimit) noexcept
Returns true if a value is at least zero, and also below a specified upper limit.
constexpr int numElementsInArray(Type(&)[N]) noexcept
Handy function for getting the number of elements in a simple const C array.
long long int64
A platform-independent 64-bit integer type.
std::u16string toString(NumberT value)
convert an number to an UTF-16 string