#include "../vjson.h"
#include <gtest/gtest.h>
void CheckGetWrongType( const vjson::Value &obj, const char *key )
{
const vjson::Value &val = obj.AtKey( key );
const char *light_fantastic = "The Light Fantastic";
if ( !val.IsBool() )
{
EXPECT_TRUE( val.AsBool( true ) );
EXPECT_FALSE( val.AsBool( false ) );
EXPECT_TRUE( obj.BoolAtKey( key, true ) );
EXPECT_FALSE( obj.BoolAtKey( key, false ) );
}
if ( !val.IsString() )
{
EXPECT_EQ( val.AsCString( nullptr ), nullptr );
EXPECT_EQ( obj.CStringAtKey( key, nullptr ), nullptr );
EXPECT_EQ( (void *)val.AsCString( light_fantastic ), (void *)light_fantastic );
EXPECT_EQ( (void *)obj.CStringAtKey( key, light_fantastic ), (void *)light_fantastic );
EXPECT_EQ( val.AsString( "" ), "" );
EXPECT_EQ( val.AsString( light_fantastic + 4 ), "Light Fantastic" );
EXPECT_EQ( obj.StringAtKey( key, "" ), "" );
EXPECT_EQ( obj.StringAtKey( key, light_fantastic + 4 ), "Light Fantastic" );
}
}
TEST(Misc, Basic) {
vjson::ParseContext ctx;
ctx.allow_trailing_comma = true;
vjson::Object doc;
EXPECT_TRUE( doc.ParseJSON(
R"JSON({
"null": null,
"true": true,
"false": false,
"empty_string": "",
"true_string": "true",
"false_string": "false",
"zero": 0,
"one": 1,
"negative_one": -1,
"zero_float": 0.0,
"float": 123.45,
"uint64_as_string": "123456789",
"big_double": 12345678900000,
"double_exponents": [ 123e45, 1.23e45, 123e-45, 1.23E-45 ],
"neg_double_exponents": [ -123e45, -1.23e45, -123e-45, -1.23E-45 ],
"empty_array": [],
"array_123": [ 1, 2, 3 ],
"empty_object": {},
"string_escaped_characters": "tab\tand\nnewline",
"tab\tin\tkey": null,
"array_of_objects": [
{ "key1": "value1" },
{ "key2": 2 },
{ "key3": false },
{ "key4": [ "hello", "world" ] },
]
})JSON", &ctx ) ) << "Parse failed line " << ctx.error_line << ": " << ctx.error_message;
ASSERT_EQ( doc.Type(), vjson::kObject );
EXPECT_TRUE( doc.IsObject() );
EXPECT_TRUE( &doc.GetObject() == &doc );
EXPECT_TRUE( &doc.AsObjectOrEmpty() == &doc );
EXPECT_TRUE( doc.AsObjectPtr() == &doc );
EXPECT_TRUE( doc.AsArrayPtr() == nullptr );
bool boolVal;
std::string stringVal;
double doubleVal;
int intVal;
uint64_t uint64Val;
{
ASSERT_TRUE( doc.HasKey( "null" ) );
EXPECT_TRUE( doc.ValuePtrAtKey("null")->IsNull() );
EXPECT_TRUE( doc["null"].IsNull() );
EXPECT_TRUE( doc["null"].Is<std::nullptr_t>() );
CheckGetWrongType( doc, "null" );
boolVal = true;
EXPECT_EQ( doc.TryInterpretAtKey( "null", boolVal ), vjson::kOK );
EXPECT_FALSE( boolVal );
doubleVal = 123;
EXPECT_EQ( doc.TryInterpretAtKey( "null", doubleVal ), vjson::kOK );
EXPECT_EQ( doubleVal, 0.0 );
stringVal = "hello";
EXPECT_EQ( doc.TryInterpretAtKey( "null", stringVal ), vjson::kOK );
EXPECT_EQ( stringVal, "" );
}
{
ASSERT_TRUE( doc.HasKey( "true" ) );
EXPECT_TRUE( doc["true"].IsBool() );
EXPECT_TRUE( doc["true"].Is<bool>() );
EXPECT_TRUE( doc["true"].AsBool( false ) );
EXPECT_TRUE( doc["true"].GetBool() );
CheckGetWrongType( doc, "true" );
boolVal = false;
EXPECT_EQ( doc["true"].TryInterpret( boolVal ), vjson::kOK );
EXPECT_TRUE( boolVal );
boolVal = false;
EXPECT_EQ( doc.TryInterpretAtKey( "true", boolVal ), vjson::kOK );
EXPECT_TRUE( boolVal );
stringVal = "hello";
EXPECT_EQ( doc.TryInterpretAtKey( "true", stringVal ), vjson::kOK );
EXPECT_EQ( stringVal, "true" );
}
{
ASSERT_TRUE( doc.HasKey( "false" ) );
EXPECT_TRUE( doc["false"].IsBool() );
EXPECT_TRUE( doc["false"].Is<bool>() );
EXPECT_FALSE( doc["false"].AsBool( true ) );
EXPECT_FALSE( doc["false"].GetBool() );
CheckGetWrongType( doc, "false" );
boolVal = true;
EXPECT_EQ( doc["false"].TryInterpret( boolVal ), vjson::kOK );
EXPECT_FALSE( boolVal );
EXPECT_FALSE( doc.BoolAtKey( "false", true ) );
boolVal = true;
EXPECT_EQ( doc.TryInterpretAtKey( "false", boolVal ), vjson::kOK );
EXPECT_FALSE( boolVal );
}
{
ASSERT_TRUE( doc.HasKey( "empty_string" ) );
EXPECT_TRUE( doc["empty_string"].IsString() );
EXPECT_TRUE( doc["empty_string"].Is<std::string>() );
EXPECT_TRUE( doc["empty_string"].Is<const char *>() );
EXPECT_TRUE( doc["empty_string"].AsString( "a non-empty string" ).empty() );
EXPECT_TRUE( doc["empty_string"].GetString().empty() );
EXPECT_TRUE( doc["empty_string"].AsCString( "a non-empty string" ) == doc["empty_string"].GetString().c_str() );
EXPECT_TRUE( doc["empty_string"].AsCString( nullptr ) == doc["empty_string"].GetString().c_str() );
EXPECT_TRUE( doc["empty_string"].GetCString() == doc["empty_string"].AsCString( nullptr ) );
CheckGetWrongType( doc, "empty_string" );
boolVal = false;
EXPECT_EQ( doc["empty_string"].TryInterpret( boolVal ), vjson::kWrongType );
EXPECT_FALSE( boolVal );
boolVal = true;
EXPECT_EQ( doc.TryInterpretAtKey( "empty_string", boolVal ), vjson::kWrongType );
EXPECT_TRUE( boolVal );
stringVal = "nonempty";
EXPECT_EQ( doc["empty_string"].TryInterpret( stringVal ), vjson::kOK );
EXPECT_TRUE( stringVal.empty() );
stringVal = "nonempty";
EXPECT_EQ( doc.TryInterpretAtKey( "empty_string", stringVal ), vjson::kOK );
EXPECT_TRUE( stringVal.empty() );
}
{
ASSERT_TRUE( doc.HasKey( "true_string" ) );
EXPECT_TRUE( doc["true_string"].IsString() );
EXPECT_STREQ( doc["true_string"].AsString( "Jabberwocky" ).c_str(), "true" );
EXPECT_STREQ( doc["true_string"].GetString().c_str(), "true" );
EXPECT_TRUE( doc["true_string"].AsCString( nullptr ) == doc["true_string"].GetString().c_str() );
CheckGetWrongType( doc, "true_string" );
stringVal = "bogus";
EXPECT_EQ( doc["true_string"].TryInterpret( stringVal ), vjson::kOK );
EXPECT_EQ( stringVal, "true" );
boolVal = false;
EXPECT_EQ( doc["true_string"].TryInterpret( boolVal ), vjson::kOK );
EXPECT_TRUE( boolVal );
EXPECT_FALSE( doc["true_string"].AsBool( false ) );
}
EXPECT_FALSE( doc.HasKey( "bogus_key" ) );
}
TEST(Print, StringEscaping) {
vjson::Object obj;
obj["newline"] = "line1\nline2";
obj["cr"] = "line1\rline2";
obj["tab"] = "col1\tcol2";
obj["backslash"] = "a\\b";
obj["quote"] = "say \"hello\"";
obj["backspace"] = "a\bb";
obj["formfeed"] = "a\fb";
obj["control"] = std::string( "a\x01" "b", 3 );
vjson::PrintOptions minified;
minified.indent = nullptr;
std::string json = obj.PrintJSON( minified );
for ( size_t i = 0; i < json.size(); ++i )
{
char c = json[i];
if ( c == '"' )
{
++i;
while ( i < json.size() && json[i] != '"' )
{
if ( json[i] == '\\' ) ++i;
++i;
}
continue;
}
EXPECT_GE( (int)(unsigned char)c, 0x20 )
<< "raw control char 0x" << std::hex << (int)(unsigned char)c
<< " at position " << std::dec << i << " in: " << json;
}
EXPECT_NE( json.find( "\\n" ), std::string::npos ) << "newline not escaped in: " << json;
EXPECT_NE( json.find( "\\r" ), std::string::npos ) << "CR not escaped in: " << json;
EXPECT_NE( json.find( "\\t" ), std::string::npos ) << "tab not escaped in: " << json;
EXPECT_NE( json.find( "\\\\" ), std::string::npos ) << "backslash not escaped in: " << json;
EXPECT_NE( json.find( "\\\"" ), std::string::npos ) << "quote not escaped in: " << json;
EXPECT_NE( json.find( "\\b" ), std::string::npos ) << "backspace not escaped in: " << json;
EXPECT_NE( json.find( "\\f" ), std::string::npos ) << "formfeed not escaped in: " << json;
EXPECT_NE( json.find( "\\u0001" ), std::string::npos ) << "control char not escaped in: " << json;
vjson::Object roundtrip;
vjson::ParseContext ctx;
ASSERT_TRUE( roundtrip.ParseJSON( json, &ctx ) )
<< "Round-trip parse failed: " << ctx.error_message << " at line " << ctx.error_line
<< "\nJSON: " << json;
EXPECT_EQ( roundtrip.StringAtKey( "newline", "" ), "line1\nline2" );
EXPECT_EQ( roundtrip.StringAtKey( "cr", "" ), "line1\rline2" );
EXPECT_EQ( roundtrip.StringAtKey( "tab", "" ), "col1\tcol2" );
EXPECT_EQ( roundtrip.StringAtKey( "backslash", "" ), "a\\b" );
EXPECT_EQ( roundtrip.StringAtKey( "quote", "" ), "say \"hello\"" );
EXPECT_EQ( roundtrip.StringAtKey( "backspace", "" ), "a\bb" );
EXPECT_EQ( roundtrip.StringAtKey( "formfeed", "" ), "a\fb" );
EXPECT_EQ( roundtrip.StringAtKey( "control", "" ), std::string( "a\x01" "b", 3 ) );
}
TEST(Print, RoundTrip) {
const char *original = R"JSON({
"string": "hello world",
"number": 42,
"float": 3.14,
"bool_true": true,
"bool_false": false,
"null_val": null,
"array": [ 1, 2, 3 ],
"nested": { "a": "alpha", "b": 2 }
})JSON";
vjson::Object obj;
vjson::ParseContext ctx;
ASSERT_TRUE( obj.ParseJSON( original, &ctx ) )
<< "Initial parse failed: " << ctx.error_message;
std::string pretty = obj.PrintJSON();
vjson::Object obj2;
ASSERT_TRUE( obj2.ParseJSON( pretty, &ctx ) )
<< "Pretty round-trip parse failed: " << ctx.error_message
<< "\nJSON:\n" << pretty;
EXPECT_EQ( obj2.StringAtKey( "string", "" ), "hello world" );
EXPECT_EQ( obj2.DoubleAtKey( "number", 0.0 ), 42.0 );
EXPECT_EQ( obj2.DoubleAtKey( "float", 0.0 ), 3.14 );
EXPECT_TRUE( obj2.BoolAtKey( "bool_true", false ) );
EXPECT_FALSE( obj2.BoolAtKey( "bool_false", true ) );
EXPECT_TRUE( obj2.ValuePtrAtKey( "null_val" ) && obj2["null_val"].IsNull() );
EXPECT_EQ( obj2["array"].ArrayLen(), 3 );
EXPECT_EQ( obj2["nested"].StringAtKey( "a", "" ), "alpha" );
vjson::PrintOptions minified;
minified.indent = nullptr;
std::string mini = obj.PrintJSON( minified );
vjson::Object obj3;
ASSERT_TRUE( obj3.ParseJSON( mini, &ctx ) )
<< "Minified round-trip parse failed: " << ctx.error_message
<< "\nJSON: " << mini;
EXPECT_EQ( obj3.StringAtKey( "string", "" ), "hello world" );
EXPECT_EQ( obj3.DoubleAtKey( "number", 0.0 ), 42.0 );
}
TEST(Print, PrettyPrintIndent) {
vjson::Object obj;
obj["alpha"] = "one";
obj["beta"] = "two";
obj["gamma"] = "three";
vjson::PrintOptions tabs;
tabs.indent = "\t";
std::string json = obj.PrintJSON( tabs );
EXPECT_NE( json.find( "\t\"alpha\"" ), std::string::npos )
<< "first key not indented\nJSON:\n" << json;
EXPECT_NE( json.find( "\t\"beta\"" ), std::string::npos )
<< "second key not indented\nJSON:\n" << json;
EXPECT_NE( json.find( "\t\"gamma\"" ), std::string::npos )
<< "third key not indented\nJSON:\n" << json;
vjson::Array arr;
arr.push_back( "x" );
arr.push_back( "y" );
vjson::Object outer;
outer["items"] = arr;
std::string json2 = outer.PrintJSON( tabs );
EXPECT_NE( json2.find( "\t\t\"x\"" ), std::string::npos )
<< "first array element not double-indented\nJSON:\n" << json2;
EXPECT_NE( json2.find( "\t\t\"y\"" ), std::string::npos )
<< "second array element not double-indented\nJSON:\n" << json2;
}
TEST(Print, Minified) {
vjson::Object obj;
obj["a"] = 1.0;
obj["b"] = "two";
vjson::PrintOptions minified;
minified.indent = nullptr;
std::string json = obj.PrintJSON( minified );
EXPECT_EQ( json.find( '\n' ), std::string::npos ) << "newline in minified: " << json;
EXPECT_EQ( json.find( '\t' ), std::string::npos ) << "tab in minified: " << json;
EXPECT_EQ( json.find( ": " ), std::string::npos ) << "space after colon in minified: " << json;
EXPECT_EQ( json.find( ", " ), std::string::npos ) << "space after comma in minified: " << json;
vjson::Object roundtrip;
vjson::ParseContext ctx;
ASSERT_TRUE( roundtrip.ParseJSON( json, &ctx ) );
EXPECT_EQ( roundtrip.DoubleAtKey( "a", 0.0 ), 1.0 );
EXPECT_EQ( roundtrip.StringAtKey( "b", "" ), "two" );
}
TEST(Misc, DoubleAtIndex) {
vjson::Array arr;
arr.push_back( 1.0 );
arr.push_back( 2.0 );
arr.push_back( 3.14 );
arr.push_back( -7.5 );
EXPECT_EQ( arr.DoubleAtIndex( 0, 0.0 ), 1.0 );
EXPECT_EQ( arr.DoubleAtIndex( 1, 0.0 ), 2.0 );
EXPECT_EQ( arr.DoubleAtIndex( 2, 0.0 ), 3.14 );
EXPECT_EQ( arr.DoubleAtIndex( 3, 0.0 ), -7.5 );
EXPECT_EQ( arr.DoubleAtIndex( 99, -1.0 ), -1.0 );
}
TEST(Array, IterTyped) {
vjson::Array strs;
strs.push_back( "alpha" );
strs.push_back( "beta" );
strs.push_back( "gamma" );
std::vector<std::string> got;
for ( const char *s: strs.Iter<const char *>() )
got.push_back( s );
ASSERT_EQ( got.size(), 3u );
EXPECT_EQ( got[0], "alpha" );
EXPECT_EQ( got[1], "beta" );
EXPECT_EQ( got[2], "gamma" );
std::vector<std::string> got2;
for ( const std::string &s: strs.Iter<std::string>() )
got2.push_back( s );
EXPECT_EQ( got2, got );
vjson::Array nums;
nums.push_back( 1.0 );
nums.push_back( 2.5 );
nums.push_back( -3.0 );
double sum = 0.0;
for ( double d: nums.Iter<double>() )
sum += d;
EXPECT_EQ( sum, 0.5 );
}
TEST(Array, IterSkipsWrongType) {
vjson::Array arr;
arr.push_back( "first" ); arr.push_back( 42.0 ); arr.push_back( true ); arr.push_back( vjson::Value{} ); arr.push_back( "second" ); vjson::Object inner;
inner["x"] = 1.0;
arr.push_back( inner ); vjson::Array nested;
nested.push_back( 99.0 );
arr.push_back( nested );
std::vector<std::string> strings;
for ( const char *s: arr.Iter<const char *>() )
strings.push_back( s );
ASSERT_EQ( strings.size(), 2u );
EXPECT_EQ( strings[0], "first" );
EXPECT_EQ( strings[1], "second" );
std::vector<double> doubles;
for ( double d: arr.Iter<double>() )
doubles.push_back( d );
ASSERT_EQ( doubles.size(), 1u );
EXPECT_EQ( doubles[0], 42.0 );
int bool_count = 0;
bool bool_val = false;
for ( bool b: arr.Iter<bool>() )
{
bool_val = b;
++bool_count;
}
EXPECT_EQ( bool_count, 1 );
EXPECT_TRUE( bool_val );
int obj_count = 0;
for ( const vjson::Object &o: arr.Iter<vjson::Object>() )
{
EXPECT_EQ( o.DoubleAtKey( "x", 0.0 ), 1.0 );
++obj_count;
}
EXPECT_EQ( obj_count, 1 );
int arr_count = 0;
for ( const vjson::Array &a: arr.Iter<vjson::Array>() )
{
EXPECT_EQ( a.DoubleAtIndex( 0, 0.0 ), 99.0 );
++arr_count;
}
EXPECT_EQ( arr_count, 1 );
}
TEST(Array, IterEmpty) {
vjson::Array empty;
int count = 0;
for ( const char *s: empty.Iter<const char *>() )
(void)s, ++count;
EXPECT_EQ( count, 0 );
vjson::Array nums;
nums.push_back( 1.0 );
nums.push_back( 2.0 );
count = 0;
for ( const char *s: nums.Iter<const char *>() )
(void)s, ++count;
EXPECT_EQ( count, 0 );
}
TEST(Array, IterMutable) {
vjson::Array arr;
arr.push_back( 1.0 );
arr.push_back( "skip me" );
arr.push_back( 2.0 );
arr.push_back( 3.0 );
for ( double &d: arr.Iter<double>() )
d *= 10.0;
EXPECT_EQ( arr.DoubleAtIndex( 0, 0.0 ), 10.0 );
EXPECT_EQ( arr.StringAtIndex( 1, "" ), "skip me" ); EXPECT_EQ( arr.DoubleAtIndex( 2, 0.0 ), 20.0 );
EXPECT_EQ( arr.DoubleAtIndex( 3, 0.0 ), 30.0 );
}
TEST(Array, IterObjectsParsed) {
vjson::Value doc;
vjson::ParseContext ctx;
ASSERT_TRUE( doc.ParseJSON(
R"JSON([
{ "name": "Alice", "score": 10 },
42,
{ "name": "Bob", "score": 20 },
"garbage",
{ "name": "Carol", "score": 30 }
])JSON", &ctx ) ) << ctx.error_message;
std::vector<std::string> names;
double total = 0.0;
for ( const vjson::Object &o: doc.AsArrayOrEmpty().Iter<vjson::Object>() )
{
names.push_back( o.StringAtKey( "name", "" ) );
total += o.DoubleAtKey( "score", 0.0 );
}
ASSERT_EQ( names.size(), 3u );
EXPECT_EQ( names[0], "Alice" );
EXPECT_EQ( names[1], "Bob" );
EXPECT_EQ( names[2], "Carol" );
EXPECT_EQ( total, 60.0 );
}
TEST(Object, RangeFor) {
vjson::Object obj;
obj["banana"] = 2.0;
obj["apple"] = 1.0;
obj["cherry"] = 3.0;
std::vector<std::string> keys;
std::vector<double> vals;
for ( const auto &item: obj )
{
keys.push_back( item.first );
vals.push_back( item.second.AsDouble( -1.0 ) );
}
ASSERT_EQ( keys.size(), 3u );
EXPECT_EQ( keys[0], "apple" ); EXPECT_EQ( vals[0], 1.0 );
EXPECT_EQ( keys[1], "banana" ); EXPECT_EQ( vals[1], 2.0 );
EXPECT_EQ( keys[2], "cherry" ); EXPECT_EQ( vals[2], 3.0 );
}
TEST(Object, RangeForMixedTypes) {
vjson::Object obj;
obj["name"] = "Alice";
obj["score"] = 42.0;
obj["active"] = true;
obj["tag"] = "player";
obj["level"] = 7.0;
std::vector<std::string> strings;
for ( const auto &item: obj )
if ( item.second.IsString() )
strings.push_back( item.second.GetString() );
ASSERT_EQ( strings.size(), 2u );
EXPECT_EQ( strings[0], "Alice" ); EXPECT_EQ( strings[1], "player" );
double total = 0.0;
for ( const auto &item: obj )
if ( item.second.IsDouble() )
total += item.second.GetDouble();
EXPECT_EQ( total, 49.0 ); }
TEST(Object, RangeForMutable) {
vjson::Object obj;
obj["x"] = 1.0;
obj["y"] = 2.0;
obj["z"] = "leave me";
for ( auto &item: obj )
if ( item.second.IsDouble() )
item.second.GetDouble() *= 10.0;
EXPECT_EQ( obj.DoubleAtKey( "x", 0.0 ), 10.0 );
EXPECT_EQ( obj.DoubleAtKey( "y", 0.0 ), 20.0 );
EXPECT_EQ( obj.StringAtKey( "z", "" ), "leave me" ); }
static void CheckParseError( const char *input, int expected_line, int expected_byte,
const char *msg_substr = nullptr )
{
vjson::Value val;
vjson::ParseContext ctx;
EXPECT_FALSE( val.ParseJSON( input, &ctx ) )
<< "Expected parse error but succeeded on: " << input;
EXPECT_EQ( ctx.error_line, expected_line ) << "input: " << input;
EXPECT_EQ( ctx.error_byte_offset, expected_byte ) << "input: " << input;
if ( msg_substr )
EXPECT_NE( ctx.error_message.find( msg_substr ), std::string::npos )
<< "Expected '" << msg_substr << "' in error '" << ctx.error_message << "'";
}
TEST(ParseErrors, UnexpectedEOF) {
CheckParseError( "", 1, 0, "Unexpected end-of-input" );
CheckParseError( "[", 1, 1, "Unexpected end-of-input" );
CheckParseError( "{", 1, 1, "Unexpected end-of-input" );
CheckParseError( "{\"a\":", 1, 5, "Unexpected end-of-input" );
CheckParseError( "[\n\n\n", 4, 4, "Unexpected end-of-input" );
}
TEST(ParseErrors, StringErrors) {
CheckParseError( "\"hello", 1, 1, "Unterminated string" );
CheckParseError( "\"abc\ndef\"", 1, 4, "Newline character" );
CheckParseError( "\"ab\x01" "cd\"", 1, 3, "Control character" );
CheckParseError( R"("\q")", 1, 2, "Invalid escape sequence" );
CheckParseError( "\"\\ \"", 1, 2, "not valid after" );
}
TEST(ParseErrors, UEscapeErrors) {
CheckParseError( R"("\u")", 1, 2, "End of input during" );
CheckParseError( R"("\u00zz")", 1, 5, "not a hex digit" );
}
TEST(ParseErrors, ObjectSyntax) {
CheckParseError( "{abc}", 1, 1, "Expected '\"'" );
CheckParseError( "{\"a\" \"b\":2}", 1, 5, "Expected ':'" );
CheckParseError( "{\"a\":1 \"b\":2}", 1, 7, "Expected '}' or ','" );
CheckParseError( "{\"a\":1,}", 1, 7, "trailing comma not permitted" );
}
TEST(ParseErrors, ArraySyntax) {
CheckParseError( "[1 2]", 1, 3, "Expected ']' or ','" );
CheckParseError( "[1,]", 1, 3, "trailing comma not permitted" );
}
TEST(ParseErrors, NumberErrors) {
CheckParseError( "-a", 1, 1, "Expected digit after '-'" );
CheckParseError( "01", 1, 0, "Leading zeros" );
CheckParseError( "1ex", 1, 2, "Digit is required after exponent" );
CheckParseError( std::string( 300, '1' ).c_str(), 1, 0, "too many characters" );
}
TEST(ParseErrors, ToplevelErrors) {
CheckParseError( "@", 1, 0, "not a valid JSON value" );
CheckParseError( "1 2", 1, 2, "Extra text" );
}
TEST(ParseErrors, ObjectParseTyped) {
vjson::Object obj;
vjson::ParseContext ctx;
EXPECT_FALSE( obj.ParseJSON( "[]", &ctx ) );
EXPECT_EQ( ctx.error_line, 1 );
EXPECT_EQ( ctx.error_byte_offset, 0 );
EXPECT_NE( ctx.error_message.find( "Failed to parse JSON object" ), std::string::npos )
<< "error was: " << ctx.error_message;
}
TEST(ParseErrors, LineNumbers) {
CheckParseError( "[\n\n\n", 4, 4, "Unexpected end-of-input" );
CheckParseError( "{\n abc}", 2, 4, "Expected '\"'" );
CheckParseError( "[\n\n garbage\n]", 3, 5, "not a valid JSON value" );
}
TEST(ParseErrors, MinifiedVsPretty) {
CheckParseError( "{\"a\":1 \"b\":2}", 1, 7, "Expected '}' or ','" );
CheckParseError( "{\n \"a\": 1\n \"b\": 2\n}", 3, 13, "Expected '}' or ','" );
}
TEST(Misc, ReadmeExample) {
const char *response = R"({
"ranked": 1,
"server": "Seattle #4",
"match_id": "14889406635632900096",
"players": [
{ "name": "Alice", "score": 1500 },
42,
{ "name": "Bob", "score": 1200 },
{ "name": "Carl" }
],
"top_scores": []
})";
vjson::Object doc;
vjson::ParseContext ctx;
ASSERT_TRUE( doc.ParseJSON( response, &ctx ) ) << ctx.error_message;
bool ranked = doc.InterpretAsBoolAtKey( "ranked", false );
EXPECT_TRUE( ranked );
double timeout = doc.DoubleAtKey( "timeout_ms", 5000.0 );
EXPECT_EQ( timeout, 5000.0 );
int spectator_count = 0;
for ( const vjson::Object &p : doc.ArrayAtKeyOrEmpty( "spectators" ).Iter<vjson::Object>() )
(void)p, ++spectator_count;
EXPECT_EQ( spectator_count, 0 );
std::vector<std::string> names;
std::vector<double> scores;
for ( const vjson::Object &p : doc.ArrayAtKeyOrEmpty( "players" ).Iter<vjson::Object>() )
{
names.push_back( p.StringAtKey( "name", "?" ) );
scores.push_back( p.DoubleAtKey( "score", 0.0 ) );
}
ASSERT_EQ( names.size(), 3u );
EXPECT_EQ( names[0], "Alice" ); EXPECT_EQ( scores[0], 1500.0 );
EXPECT_EQ( names[1], "Bob" ); EXPECT_EQ( scores[1], 1200.0 );
EXPECT_EQ( names[2], "Carl" ); EXPECT_EQ( scores[2], 0.0 );
const char *leader = doc["top_scores"].AtIndex(0).CStringAtKey( "name", "(nobody)" );
EXPECT_STREQ( leader, "(nobody)" );
uint64_t match_id = doc.InterpretAsUint64AtKey( "match_id", 0 );
EXPECT_EQ( match_id, 14889406635632900096ull );
}
int main(int argc, char **argv) {
::testing::InitGoogleTest( &argc, argv );
return RUN_ALL_TESTS();
}