35 case '{':
return parseObject (t, result);
36 case '[':
return parseArray (t, result);
39 return createFail (
"Expected '{' or '['", &t);
64 case 'a': c =
'\a';
break;
65 case 'b': c =
'\b';
break;
66 case 'f': c =
'\f';
break;
67 case 'n': c =
'\n';
break;
68 case 'r': c =
'\r';
break;
69 case 't': c =
'\t';
break;
75 for (
int i = 4; --i >= 0;)
80 return createFail (
"Syntax error in unicode escape sequence");
82 c = (juce_wchar) ((c << 4) +
static_cast<juce_wchar
> (digitValue));
91 return createFail (
"Unexpected end-of-input in string constant");
105 switch (t2.getAndAdvance())
107 case '{': t = t2;
return parseObject (t, result);
108 case '[': t = t2;
return parseArray (t, result);
109 case '"': t = t2;
return parseString (
'"', t, result);
110 case '\'': t = t2;
return parseString (
'\'', t, result);
113 t2 = t2.findEndOfWhitespace();
118 return parseNumber (t, result,
true);
120 case '0':
case '1':
case '2':
case '3':
case '4':
121 case '5':
case '6':
case '7':
case '8':
case '9':
122 return parseNumber (t, result,
false);
125 if (t2.getAndAdvance() ==
'r' && t2.getAndAdvance() ==
'u' && t2.getAndAdvance() ==
'e')
134 if (t2.getAndAdvance() ==
'a' && t2.getAndAdvance() ==
'l'
135 && t2.getAndAdvance() ==
's' && t2.getAndAdvance() ==
'e')
138 result =
var (
false);
144 if (t2.getAndAdvance() ==
'u' && t2.getAndAdvance() ==
'l' && t2.getAndAdvance() ==
'l')
156 return createFail (
"Syntax error", &t);
163 if (location !=
nullptr)
164 m <<
": \"" <<
String (*location, 20) <<
'"';
174 jassert (intValue >= 0 && intValue < 10);
178 auto previousChar = t;
180 auto digit = ((int) c) -
'0';
182 if (isPositiveAndBelow (digit, 10))
184 intValue = intValue * 10 + digit;
188 if (c ==
'e' || c ==
'E' || c ==
'.')
192 result = isNegative ? -asDouble : asDouble;
197 || c ==
',' || c ==
'}' || c ==
']' || c == 0)
203 return createFail (
"Syntax error in number", &oldT);
206 auto correctedValue = isNegative ? -intValue : intValue;
208 if ((intValue >> 31) != 0)
209 result = correctedValue;
211 result = (int) correctedValue;
219 result = resultObject;
220 auto& resultProperties = resultObject->getProperties();
233 return createFail (
"Unexpected end-of-input in object declaration");
238 auto r = parseString (
'"', t, propertyNameVar);
243 const Identifier propertyName (propertyNameVar.toString());
253 return createFail (
"Expected ':', but found", &oldT);
255 resultProperties.set (propertyName,
var());
256 var* propertyValue = resultProperties.getVarPointer (propertyName);
258 auto r2 = parseAny (t, *propertyValue);
276 return createFail (
"Expected object member declaration, but found", &oldT);
285 auto* destArray = result.
getArray();
298 return createFail (
"Unexpected end-of-input in array declaration");
301 destArray->add (
var());
302 auto r = parseAny (t, destArray->getReference (destArray->size() - 1));
318 return createFail (
"Expected object array item, but found", &oldT);
329 int indentLevel,
bool allOnOneLine,
int maximumDecimalPlaces)
341 else if (v.isUndefined())
347 out << (static_cast<bool> (v) ?
"true" :
"false");
349 else if (v.isDouble())
351 auto d =
static_cast<double> (v);
353 if (juce_isfinite (d))
355 out << serialiseDouble (d);
362 else if (v.isArray())
364 writeArray (out, *v.
getArray(), indentLevel, allOnOneLine, maximumDecimalPlaces);
366 else if (v.isObject())
368 if (
auto*
object = v.getDynamicObject())
369 object->writeAsJSON (out, indentLevel, allOnOneLine, maximumDecimalPlaces);
376 jassert (! (v.isMethod() || v.isBinaryData()));
382 static void writeEscapedChar (
OutputStream& out,
const unsigned short value)
397 case '\"': out <<
"\\\"";
break;
398 case '\\': out <<
"\\\\";
break;
399 case '\a': out <<
"\\a";
break;
400 case '\b': out <<
"\\b";
break;
401 case '\f': out <<
"\\f";
break;
402 case '\t': out <<
"\\t";
break;
403 case '\r': out <<
"\\r";
break;
404 case '\n': out <<
"\\n";
break;
407 if (c >= 32 && c < 127)
415 CharPointer_UTF16::CharType chars[2];
419 for (
int i = 0; i < 2; ++i)
420 writeEscapedChar (out, (
unsigned short) chars[i]);
424 writeEscapedChar (out, (
unsigned short) c);
433 static void writeSpaces (
OutputStream& out,
int numSpaces)
439 int indentLevel,
bool allOnOneLine,
int maximumDecimalPlaces)
448 for (
int i = 0; i < array.
size(); ++i)
451 writeSpaces (out, indentLevel + indentSize);
453 write (out, array.
getReference(i), indentLevel + indentSize, allOnOneLine, maximumDecimalPlaces);
455 if (i < array.
size() - 1)
460 out <<
',' << newLine;
462 else if (! allOnOneLine)
467 writeSpaces (out, indentLevel);
473 enum { indentSize = 2 };
481 if (!
parse (text, result))
491 if (! JSONParser::parseAny (text.
text, result))
509 return JSONParser::parseObjectOrArray (text.
getCharPointer(), result);
515 JSONFormatter::write (mo, data, 0, allOnOneLine, maximumDecimalPlaces);
521 JSONFormatter::write (output, data, 0, allOnOneLine, maximumDecimalPlaces);
527 JSONFormatter::writeString (mo, s.
text);
535 if (quote ==
'"' || quote ==
'\'')
536 return JSONParser::parseString (quote, t, result);
548 JSONTests() :
UnitTest (
"JSON",
"JSON") {}
550 static String createRandomWideCharString (Random& r)
552 juce_wchar buffer[40] = { 0 };
554 for (
int i = 0; i < numElementsInArray (buffer) - 1; ++i)
560 buffer[i] = (juce_wchar) (1 + r.nextInt (0x10ffff - 1));
562 while (! CharPointer_UTF16::canRepresent (buffer[i]));
565 buffer[i] = (juce_wchar) (1 + r.nextInt (0xff));
568 return CharPointer_UTF32 (buffer);
571 static String createRandomIdentifier (Random& r)
573 char buffer[30] = { 0 };
575 for (
int i = 0; i < numElementsInArray (buffer) - 1; ++i)
577 static const char chars[] =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-:";
578 buffer[i] = chars [r.nextInt (
sizeof (chars) - 1)];
581 return CharPointer_ASCII (buffer);
586 static var createRandomDouble (Random& r)
588 return var ((r.nextDouble() * 1000.0) + 0.1);
591 static var createRandomVar (Random& r,
int depth)
593 switch (r.nextInt (depth > 3 ? 6 : 8))
596 case 1:
return r.nextInt();
597 case 2:
return r.nextInt64();
598 case 3:
return r.nextBool();
599 case 4:
return createRandomDouble (r);
600 case 5:
return createRandomWideCharString (r);
604 var v (createRandomVar (r, depth + 1));
606 for (
int i = 1 + r.nextInt (30); --i >= 0;)
607 v.append (createRandomVar (r, depth + 1));
614 DynamicObject* o =
new DynamicObject();
616 for (
int i = r.nextInt (30); --i >= 0;)
617 o->setProperty (createRandomIdentifier (r), createRandomVar (r, depth + 1));
627 void runTest()
override
632 Random r = getRandom();
634 expect (JSON::parse (String()) == var());
635 expect (JSON::parse (
"{}").isObject());
636 expect (JSON::parse (
"[]").isArray());
637 expect (JSON::parse (
"[ 1234 ]")[0].isInt());
638 expect (JSON::parse (
"[ 12345678901234 ]")[0].isInt64());
639 expect (JSON::parse (
"[ 1.123e3 ]")[0].isDouble());
640 expect (JSON::parse (
"[ -1234]")[0].isInt());
641 expect (JSON::parse (
"[-12345678901234]")[0].isInt64());
642 expect (JSON::parse (
"[-1.123e3]")[0].isDouble());
644 for (
int i = 100; --i >= 0;)
649 v = createRandomVar (r, 0);
651 const bool oneLine = r.nextBool();
652 String asString (JSON::toString (v, oneLine));
653 var parsed = JSON::parse (
"[" + asString +
"]")[0];
654 String parsedString (JSON::toString (parsed, oneLine));
655 expect (asString.isNotEmpty() && parsedString == asString);
660 beginTest (
"Float formatting");
662 std::map<double, String> tests;
665 tests[1.01] =
"1.01";
666 tests[0.76378] =
"0.76378";
667 tests[-10] =
"-10.0";
668 tests[10.01] =
"10.01";
669 tests[0.0123] =
"0.0123";
670 tests[-3.7e-27] =
"-3.7e-27";
671 tests[1e+40] =
"1.0e40";
672 tests[-12345678901234567.0] =
"-1.234567890123457e16";
673 tests[192000] =
"192000.0";
674 tests[1234567] =
"1.234567e6";
675 tests[0.00006] =
"0.00006";
676 tests[0.000006] =
"6.0e-6";
678 for (
auto& test : tests)
679 expectEquals (JSON::toString (test.first), test.second);
684static 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) const 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...
CharPointer_UTF8 findEndOfWhitespace() const noexcept
Returns the first non-whitespace character in the string.
static bool isDigit(char character) noexcept
Checks whether a character is a digit.
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 dynamically implemented object.
Represents a local file or directory.
String loadFileAsString() const
Reads a file into memory as a string.
Represents a string identifier, designed for accessing properties by name.
bool isValid() const noexcept
Returns true if this Identifier is not null.
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.
bool appendUTF8Char(juce_wchar character)
Appends the utf-8 bytes for a unicode character.
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.