- added Features class that describes allowed extension for Reader, to allow for strict configuration

- added tests from json.org jsonchecker and modified jsontestrunner to use strict parsing mode when executing them
This commit is contained in:
Baptiste Lepilleur 2009-11-18 21:38:54 +00:00
parent 64e07e54ed
commit 8868147835
46 changed files with 308 additions and 28 deletions

View File

@ -42,3 +42,6 @@ and TARGET may be:
doc: build documentation
doc-dist: build documentation tarball
To run the test manually:
cd test
python runjsontests.py "path to jsontest.exe"

42
include/json/features.h Normal file
View File

@ -0,0 +1,42 @@
#ifndef CPPTL_JSON_FEATURES_H_INCLUDED
# define CPPTL_JSON_FEATURES_H_INCLUDED
# include "forwards.h"
namespace Json {
/** \brief Configuration passed to reader and writer.
* This configuration object can be used to force the Reader or Writer
* to behave in a standard conforming way.
*/
class JSON_API Features
{
public:
/** \brief A configuration that allows all features and assumes all strings are UTF-8.
* - C & C++ comments are allowed
* - Root object can be any JSON value
* - Assumes Value strings are encoded in UTF-8
*/
static Features all();
/** \brief A configuration that is strictly compatible with the JSON specification.
* - Comments are forbidden.
* - Root object must be either an array or an object value.
* - Assumes Value strings are encoded in UTF-8
*/
static Features strictMode();
/** \brief Initialize the configuration like JsonConfig::allFeatures;
*/
Features();
/// \c true if comments are allowed. Default: \c true.
bool allowComments_;
/// \c true if root must be either an array or an object value. Default: \c false.
bool strictRoot_;
};
} // namespace Json
#endif // CPPTL_JSON_FEATURES_H_INCLUDED

View File

@ -9,6 +9,9 @@ namespace Json {
class Reader;
class StyledWriter;
// features.h
class Features;
// value.h
class StaticString;
class Path;

View File

@ -5,5 +5,6 @@
# include "value.h"
# include "reader.h"
# include "writer.h"
# include "features.h"
#endif // JSON_JSON_H_INCLUDED

View File

@ -1,7 +1,7 @@
#ifndef CPPTL_JSON_READER_H_INCLUDED
# define CPPTL_JSON_READER_H_INCLUDED
# include "forwards.h"
# include "features.h"
# include "value.h"
# include <deque>
# include <stack>
@ -10,10 +10,7 @@
namespace Json {
class Value;
/** \brief Unserialize a <a HREF="http://www.json.org">JSON</a> document into a Value.
*
*
*/
class JSON_API Reader
@ -22,14 +19,24 @@ namespace Json {
typedef char Char;
typedef const Char *Location;
/** \brief Constructs a Reader allowing all features
* for parsing.
*/
Reader();
/** \brief Constructs a Reader allowing the specified feature set
* for parsing.
*/
Reader( const Features &features );
/** \brief Read a Value from a <a HREF="http://www.json.org">JSON</a> document.
* \param document UTF-8 encoded string containing the document to read.
* \param root [out] Contains the root value of the document if it was
* successfully parsed.
* \param collectComments \c true to collect comment and allow writing them back during
* serialization, \c false to discard comments.
* This parameter is ignored if Features::allowComments_
* is \c false.
* \return \c true if the document was successfully parsed, \c false if an error occurred.
*/
bool parse( const std::string &document,
@ -42,6 +49,8 @@ namespace Json {
* successfully parsed.
* \param collectComments \c true to collect comment and allow writing them back during
* serialization, \c false to discard comments.
* This parameter is ignored if Features::allowComments_
* is \c false.
* \return \c true if the document was successfully parsed, \c false if an error occurred.
*/
bool parse( const char *beginDoc, const char *endDoc,
@ -50,7 +59,7 @@ namespace Json {
/// \brief Parse from input stream.
/// \see Json::operator>>(std::istream&, Json::Value&).
bool parse( std::istream&,
bool parse( std::istream &is,
Value &root,
bool collectComments = true );
@ -152,6 +161,7 @@ namespace Json {
Location lastValueEnd_;
Value *lastValue_;
std::string commentsBefore_;
Features features_;
bool collectComments_;
};

View File

@ -86,9 +86,11 @@ static int
parseAndSaveValueTree( const std::string &input,
const std::string &actual,
const std::string &kind,
Json::Value &root )
Json::Value &root,
const Json::Features &features,
bool parseOnly )
{
Json::Reader reader;
Json::Reader reader( features );
bool parsingSuccessful = reader.parse( input, root );
if ( !parsingSuccessful )
{
@ -98,6 +100,8 @@ parseAndSaveValueTree( const std::string &input,
return 1;
}
if ( !parseOnly )
{
FILE *factual = fopen( actual.c_str(), "wt" );
if ( !factual )
{
@ -106,6 +110,7 @@ parseAndSaveValueTree( const std::string &input,
}
printValueTree( factual, root );
fclose( factual );
}
return 0;
}
@ -143,25 +148,65 @@ removeSuffix( const std::string &path,
return path.substr( 0, path.length() - extension.length() );
}
int main( int argc, const char *argv[] )
static int
printUsage( const char *argv[] )
{
if ( argc != 2 )
{
printf( "Usage: %s input-json-file", argv[0] );
printf( "Usage: %s [--strict] input-json-file", argv[0] );
return 3;
}
std::string input = readInputTestFile( argv[1] );
int
parseCommandLine( int argc, const char *argv[],
Json::Features &features, std::string &path,
bool &parseOnly )
{
parseOnly = false;
if ( argc < 2 )
{
return printUsage( argv );
}
int index = 1;
if ( std::string(argv[1]) == "--json-checker" )
{
features = Json::Features::strictMode();
parseOnly = true;
++index;
}
if ( index == argc || index + 1 < argc )
{
return printUsage( argv );
}
path = argv[index];
return 0;
}
int main( int argc, const char *argv[] )
{
std::string path;
Json::Features features;
bool parseOnly;
int exitCode = parseCommandLine( argc, argv, features, path, parseOnly );
if ( exitCode != 0 )
{
return exitCode;
}
std::string input = readInputTestFile( path.c_str() );
if ( input.empty() )
{
printf( "Failed to read input or empty input: %s\n", argv[1] );
printf( "Failed to read input or empty input: %s\n", path.c_str() );
return 3;
}
std::string basePath = removeSuffix( argv[1], ".json" );
if ( basePath.empty() )
if ( !parseOnly && basePath.empty() )
{
printf( "Bad input path. Path does not end with '.expected':\n%s\n", argv[1] );
printf( "Bad input path. Path does not end with '.expected':\n%s\n", path.c_str() );
return 3;
}
@ -170,15 +215,16 @@ int main( int argc, const char *argv[] )
std::string rewriteActualPath = basePath + ".actual-rewrite";
Json::Value root;
int exitCode = parseAndSaveValueTree( input, actualPath, "input", root );
if ( exitCode == 0 )
exitCode = parseAndSaveValueTree( input, actualPath, "input", root, features, parseOnly );
if ( exitCode == 0 && !parseOnly )
{
std::string rewrite;
exitCode = rewriteValueTree( rewritePath, root, rewrite );
if ( exitCode == 0 )
{
Json::Value rewriteRoot;
exitCode = parseAndSaveValueTree( rewrite, rewriteActualPath, "rewrite", rewriteRoot );
exitCode = parseAndSaveValueTree( rewrite, rewriteActualPath,
"rewrite", rewriteRoot, features, parseOnly );
}
}

View File

@ -13,6 +13,36 @@
namespace Json {
// Implementation of class Features
// ////////////////////////////////
Features::Features()
: allowComments_( true )
, strictRoot_( false )
{
}
Features
Features::all()
{
return Features();
}
Features
Features::strictMode()
{
Features features;
features.allowComments_ = false;
features.strictRoot_ = true;
return features;
}
// Implementation of class Reader
// ////////////////////////////////
static inline bool
in( Reader::Char c, Reader::Char c1, Reader::Char c2, Reader::Char c3, Reader::Char c4 )
{
@ -77,9 +107,17 @@ static std::string codePointToUTF8(unsigned int cp)
// //////////////////////////////////////////////////////////////////
Reader::Reader()
: features_( Features::all() )
{
}
Reader::Reader( const Features &features )
: features_( features )
{
}
bool
Reader::parse( const std::string &document,
Value &root,
@ -91,6 +129,7 @@ Reader::parse( const std::string &document,
return parse( begin, end, root, collectComments );
}
bool
Reader::parse( std::istream& sin,
Value &root,
@ -113,6 +152,11 @@ Reader::parse( const char *beginDoc, const char *endDoc,
Value &root,
bool collectComments )
{
if ( !features_.allowComments_ )
{
collectComments = false;
}
begin_ = beginDoc;
end_ = endDoc;
collectComments_ = collectComments;
@ -130,6 +174,19 @@ Reader::parse( const char *beginDoc, const char *endDoc,
skipCommentTokens( token );
if ( collectComments_ && !commentsBefore_.empty() )
root.setComment( commentsBefore_, commentAfter );
if ( features_.strictRoot_ )
{
if ( !root.isArray() && !root.isObject() )
{
// Set error location to start of doc, ideally should be first token found in doc
token.type_ = tokenError;
token.start_ = beginDoc;
token.end_ = endDoc;
addError( "A valid JSON document must be either an array or an object value.",
token );
return false;
}
}
return successful;
}
@ -187,6 +244,8 @@ Reader::readValue()
void
Reader::skipCommentTokens( Token &token )
{
if ( features_.allowComments_ )
{
do
{
@ -194,6 +253,11 @@ Reader::skipCommentTokens( Token &token )
}
while ( token.type_ == tokenComment );
}
else
{
readToken( token );
}
}
bool

View File

@ -169,6 +169,9 @@
<File
RelativePath="..\..\include\json\config.h">
</File>
<File
RelativePath="..\..\include\json\features.h">
</File>
<File
RelativePath="..\..\include\json\forwards.h">
</File>

View File

@ -0,0 +1 @@
"A JSON payload should be an object or array, not a string."

View File

@ -0,0 +1 @@
{"Extra value after close": true} "misplaced quoted value"

View File

@ -0,0 +1 @@
{"Illegal expression": 1 + 2}

View File

@ -0,0 +1 @@
{"Illegal invocation": alert()}

View File

@ -0,0 +1 @@
{"Numbers cannot have leading zeroes": 013}

View File

@ -0,0 +1 @@
{"Numbers cannot be hex": 0x14}

View File

@ -0,0 +1 @@
["Illegal backslash escape: \x15"]

View File

@ -0,0 +1 @@
[\naked]

View File

@ -0,0 +1 @@
["Illegal backslash escape: \017"]

View File

@ -0,0 +1 @@
[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]

View File

@ -0,0 +1 @@
{"Missing colon" null}

View File

@ -0,0 +1 @@
["Unclosed array"

View File

@ -0,0 +1 @@
{"Double colon":: null}

View File

@ -0,0 +1 @@
{"Comma instead of colon", null}

View File

@ -0,0 +1 @@
["Colon instead of comma": false]

View File

@ -0,0 +1 @@
["Bad value", truth]

View File

@ -0,0 +1 @@
['single quote']

View File

@ -0,0 +1 @@
[" tab character in string "]

View File

@ -0,0 +1 @@
["tab\ character\ in\ string\ "]

View File

@ -0,0 +1,2 @@
["line
break"]

View File

@ -0,0 +1,2 @@
["line\
break"]

View File

@ -0,0 +1 @@
[0e]

View File

@ -0,0 +1 @@
{unquoted_key: "keys must be quoted"}

View File

@ -0,0 +1 @@
[0e+]

View File

@ -0,0 +1 @@
[0e+-1]

View File

@ -0,0 +1 @@
{"Comma instead if closing brace": true,

View File

@ -0,0 +1 @@
["mismatch"}

View File

@ -0,0 +1 @@
["extra comma",]

View File

@ -0,0 +1 @@
["double extra comma",,]

View File

@ -0,0 +1 @@
[ , "<-- missing value"]

View File

@ -0,0 +1 @@
["Comma after the close"],

View File

@ -0,0 +1 @@
["Extra close"]]

View File

@ -0,0 +1 @@
{"Extra comma": true,}

View File

@ -0,0 +1,58 @@
[
"JSON Test Pattern pass1",
{"object with 1 member":["array with 1 element"]},
{},
[],
-42,
true,
false,
null,
{
"integer": 1234567890,
"real": -9876.543210,
"e": 0.123456789e-12,
"E": 1.234567890E+34,
"": 23456789012E66,
"zero": 0,
"one": 1,
"space": " ",
"quote": "\"",
"backslash": "\\",
"controls": "\b\f\n\r\t",
"slash": "/ & \/",
"alpha": "abcdefghijklmnopqrstuvwyz",
"ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ",
"digit": "0123456789",
"0123456789": "digit",
"special": "`1~!@#$%^&*()_+-={':[,]}|;.</>?",
"hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A",
"true": true,
"false": false,
"null": null,
"array":[ ],
"object":{ },
"address": "50 St. James Street",
"url": "http://www.JSON.org/",
"comment": "// /* <!-- --",
"# -- --> */": " ",
" s p a c e d " :[1,2 , 3
,
4 , 5 , 6 ,7 ],"compact":[1,2,3,4,5,6,7],
"jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}",
"quotes": "&#34; \u0022 %22 0x22 034 &#x22;",
"\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?"
: "A key can be any string"
},
0.5 ,98.6
,
99.44
,
1066,
1e1,
0.1e1,
1e-1,
1e00,2e+00,2e-00
,"rosebud"]

View File

@ -0,0 +1 @@
[[[[[[[[[[[[[[[[[[["Not too deep"]]]]]]]]]]]]]]]]]]]

View File

@ -0,0 +1,6 @@
{
"JSON Test Pattern pass3": {
"The outermost value": "must be an object or array.",
"In this test": "It is an object."
}
}

View File

@ -0,0 +1,3 @@
Test suite from http://json.org/JSON_checker/.
If the JSON_checker is working correctly, it must accept all of the pass*.json files and reject all of the fail*.json files.

View File

@ -3,6 +3,7 @@ import os
import os.path
from glob import glob
RUN_JSONCHECKER = True
def compareOutputs( expected, actual, message ):
expected = expected.strip().replace('\r','').split('\n')
@ -39,7 +40,10 @@ def runAllTests( jsontest_executable_path, input_dir = None ):
if not input_dir:
input_dir = os.getcwd()
tests = glob( os.path.join( input_dir, '*.json' ) )
if RUN_JSONCHECKER:
test_jsonchecker = glob( os.path.join( input_dir, 'jsonchecker', '*.json' ) )
else:
test_jsonchecker = []
failed_tests = []
for input_path in tests + test_jsonchecker:
is_json_checker_test = input_path in test_jsonchecker
@ -54,7 +58,8 @@ def runAllTests( jsontest_executable_path, input_dir = None ):
if expect_failure:
if status is None:
print 'FAILED'
failed_tests.append( (input_path, 'Parsing should have failed') )
failed_tests.append( (input_path, 'Parsing should have failed:\n%s' %
safeReadFile(input_path)) )
else:
print 'OK'
else: