diff --git a/README.txt b/README.txt index f3aaa8e..5651d37 100644 --- a/README.txt +++ b/README.txt @@ -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" diff --git a/include/json/features.h b/include/json/features.h new file mode 100644 index 0000000..f1404f6 --- /dev/null +++ b/include/json/features.h @@ -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 diff --git a/include/json/forwards.h b/include/json/forwards.h index 3372a55..ee76071 100644 --- a/include/json/forwards.h +++ b/include/json/forwards.h @@ -9,6 +9,9 @@ namespace Json { class Reader; class StyledWriter; + // features.h + class Features; + // value.h class StaticString; class Path; diff --git a/include/json/json.h b/include/json/json.h index a539740..c71ed65 100644 --- a/include/json/json.h +++ b/include/json/json.h @@ -5,5 +5,6 @@ # include "value.h" # include "reader.h" # include "writer.h" +# include "features.h" #endif // JSON_JSON_H_INCLUDED diff --git a/include/json/reader.h b/include/json/reader.h index e113569..ee1d6a2 100644 --- a/include/json/reader.h +++ b/include/json/reader.h @@ -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 # include @@ -10,10 +10,7 @@ namespace Json { - class Value; - /** \brief Unserialize a JSON 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 JSON 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_; }; diff --git a/src/jsontestrunner/main.cpp b/src/jsontestrunner/main.cpp index 88ed2f1..231ee0c 100644 --- a/src/jsontestrunner/main.cpp +++ b/src/jsontestrunner/main.cpp @@ -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,14 +100,17 @@ parseAndSaveValueTree( const std::string &input, return 1; } - FILE *factual = fopen( actual.c_str(), "wt" ); - if ( !factual ) + if ( !parseOnly ) { - printf( "Failed to create %s actual file.\n", kind.c_str() ); - return 2; + FILE *factual = fopen( actual.c_str(), "wt" ); + if ( !factual ) + { + printf( "Failed to create %s actual file.\n", kind.c_str() ); + return 2; + } + printValueTree( factual, root ); + fclose( factual ); } - 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 [--strict] input-json-file", argv[0] ); + return 3; +} + + +int +parseCommandLine( int argc, const char *argv[], + Json::Features &features, std::string &path, + bool &parseOnly ) +{ + parseOnly = false; + if ( argc < 2 ) { - printf( "Usage: %s input-json-file", argv[0] ); - return 3; + return printUsage( argv ); } - std::string input = readInputTestFile( argv[1] ); + 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 ); } } diff --git a/src/lib_json/json_reader.cpp b/src/lib_json/json_reader.cpp index 0e0c2ff..7addb3b 100644 --- a/src/lib_json/json_reader.cpp +++ b/src/lib_json/json_reader.cpp @@ -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; } @@ -188,11 +245,18 @@ Reader::readValue() void Reader::skipCommentTokens( Token &token ) { - do + if ( features_.allowComments_ ) + { + do + { + readToken( token ); + } + while ( token.type_ == tokenComment ); + } + else { readToken( token ); } - while ( token.type_ == tokenComment ); } diff --git a/src/lib_json/lib_json.vcproj b/src/lib_json/lib_json.vcproj index 3fa9cf0..aa47ffa 100644 --- a/src/lib_json/lib_json.vcproj +++ b/src/lib_json/lib_json.vcproj @@ -169,6 +169,9 @@ + + diff --git a/test/jsonchecker/fail1.json b/test/jsonchecker/fail1.json new file mode 100644 index 0000000..6216b86 --- /dev/null +++ b/test/jsonchecker/fail1.json @@ -0,0 +1 @@ +"A JSON payload should be an object or array, not a string." \ No newline at end of file diff --git a/test/jsonchecker/fail10.json b/test/jsonchecker/fail10.json new file mode 100644 index 0000000..5d8c004 --- /dev/null +++ b/test/jsonchecker/fail10.json @@ -0,0 +1 @@ +{"Extra value after close": true} "misplaced quoted value" \ No newline at end of file diff --git a/test/jsonchecker/fail11.json b/test/jsonchecker/fail11.json new file mode 100644 index 0000000..76eb95b --- /dev/null +++ b/test/jsonchecker/fail11.json @@ -0,0 +1 @@ +{"Illegal expression": 1 + 2} \ No newline at end of file diff --git a/test/jsonchecker/fail12.json b/test/jsonchecker/fail12.json new file mode 100644 index 0000000..77580a4 --- /dev/null +++ b/test/jsonchecker/fail12.json @@ -0,0 +1 @@ +{"Illegal invocation": alert()} \ No newline at end of file diff --git a/test/jsonchecker/fail13.json b/test/jsonchecker/fail13.json new file mode 100644 index 0000000..379406b --- /dev/null +++ b/test/jsonchecker/fail13.json @@ -0,0 +1 @@ +{"Numbers cannot have leading zeroes": 013} \ No newline at end of file diff --git a/test/jsonchecker/fail14.json b/test/jsonchecker/fail14.json new file mode 100644 index 0000000..0ed366b --- /dev/null +++ b/test/jsonchecker/fail14.json @@ -0,0 +1 @@ +{"Numbers cannot be hex": 0x14} \ No newline at end of file diff --git a/test/jsonchecker/fail15.json b/test/jsonchecker/fail15.json new file mode 100644 index 0000000..fc8376b --- /dev/null +++ b/test/jsonchecker/fail15.json @@ -0,0 +1 @@ +["Illegal backslash escape: \x15"] \ No newline at end of file diff --git a/test/jsonchecker/fail16.json b/test/jsonchecker/fail16.json new file mode 100644 index 0000000..3fe21d4 --- /dev/null +++ b/test/jsonchecker/fail16.json @@ -0,0 +1 @@ +[\naked] \ No newline at end of file diff --git a/test/jsonchecker/fail17.json b/test/jsonchecker/fail17.json new file mode 100644 index 0000000..62b9214 --- /dev/null +++ b/test/jsonchecker/fail17.json @@ -0,0 +1 @@ +["Illegal backslash escape: \017"] \ No newline at end of file diff --git a/test/jsonchecker/fail18.json b/test/jsonchecker/fail18.json new file mode 100644 index 0000000..edac927 --- /dev/null +++ b/test/jsonchecker/fail18.json @@ -0,0 +1 @@ +[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]] \ No newline at end of file diff --git a/test/jsonchecker/fail19.json b/test/jsonchecker/fail19.json new file mode 100644 index 0000000..3b9c46f --- /dev/null +++ b/test/jsonchecker/fail19.json @@ -0,0 +1 @@ +{"Missing colon" null} \ No newline at end of file diff --git a/test/jsonchecker/fail2.json b/test/jsonchecker/fail2.json new file mode 100644 index 0000000..6b7c11e --- /dev/null +++ b/test/jsonchecker/fail2.json @@ -0,0 +1 @@ +["Unclosed array" \ No newline at end of file diff --git a/test/jsonchecker/fail20.json b/test/jsonchecker/fail20.json new file mode 100644 index 0000000..27c1af3 --- /dev/null +++ b/test/jsonchecker/fail20.json @@ -0,0 +1 @@ +{"Double colon":: null} \ No newline at end of file diff --git a/test/jsonchecker/fail21.json b/test/jsonchecker/fail21.json new file mode 100644 index 0000000..6247457 --- /dev/null +++ b/test/jsonchecker/fail21.json @@ -0,0 +1 @@ +{"Comma instead of colon", null} \ No newline at end of file diff --git a/test/jsonchecker/fail22.json b/test/jsonchecker/fail22.json new file mode 100644 index 0000000..a775258 --- /dev/null +++ b/test/jsonchecker/fail22.json @@ -0,0 +1 @@ +["Colon instead of comma": false] \ No newline at end of file diff --git a/test/jsonchecker/fail23.json b/test/jsonchecker/fail23.json new file mode 100644 index 0000000..494add1 --- /dev/null +++ b/test/jsonchecker/fail23.json @@ -0,0 +1 @@ +["Bad value", truth] \ No newline at end of file diff --git a/test/jsonchecker/fail24.json b/test/jsonchecker/fail24.json new file mode 100644 index 0000000..caff239 --- /dev/null +++ b/test/jsonchecker/fail24.json @@ -0,0 +1 @@ +['single quote'] \ No newline at end of file diff --git a/test/jsonchecker/fail25.json b/test/jsonchecker/fail25.json new file mode 100644 index 0000000..8b7ad23 --- /dev/null +++ b/test/jsonchecker/fail25.json @@ -0,0 +1 @@ +[" tab character in string "] \ No newline at end of file diff --git a/test/jsonchecker/fail26.json b/test/jsonchecker/fail26.json new file mode 100644 index 0000000..845d26a --- /dev/null +++ b/test/jsonchecker/fail26.json @@ -0,0 +1 @@ +["tab\ character\ in\ string\ "] \ No newline at end of file diff --git a/test/jsonchecker/fail27.json b/test/jsonchecker/fail27.json new file mode 100644 index 0000000..6b01a2c --- /dev/null +++ b/test/jsonchecker/fail27.json @@ -0,0 +1,2 @@ +["line +break"] \ No newline at end of file diff --git a/test/jsonchecker/fail28.json b/test/jsonchecker/fail28.json new file mode 100644 index 0000000..621a010 --- /dev/null +++ b/test/jsonchecker/fail28.json @@ -0,0 +1,2 @@ +["line\ +break"] \ No newline at end of file diff --git a/test/jsonchecker/fail29.json b/test/jsonchecker/fail29.json new file mode 100644 index 0000000..47ec421 --- /dev/null +++ b/test/jsonchecker/fail29.json @@ -0,0 +1 @@ +[0e] \ No newline at end of file diff --git a/test/jsonchecker/fail3.json b/test/jsonchecker/fail3.json new file mode 100644 index 0000000..168c81e --- /dev/null +++ b/test/jsonchecker/fail3.json @@ -0,0 +1 @@ +{unquoted_key: "keys must be quoted"} \ No newline at end of file diff --git a/test/jsonchecker/fail30.json b/test/jsonchecker/fail30.json new file mode 100644 index 0000000..8ab0bc4 --- /dev/null +++ b/test/jsonchecker/fail30.json @@ -0,0 +1 @@ +[0e+] \ No newline at end of file diff --git a/test/jsonchecker/fail31.json b/test/jsonchecker/fail31.json new file mode 100644 index 0000000..1cce602 --- /dev/null +++ b/test/jsonchecker/fail31.json @@ -0,0 +1 @@ +[0e+-1] \ No newline at end of file diff --git a/test/jsonchecker/fail32.json b/test/jsonchecker/fail32.json new file mode 100644 index 0000000..45cba73 --- /dev/null +++ b/test/jsonchecker/fail32.json @@ -0,0 +1 @@ +{"Comma instead if closing brace": true, \ No newline at end of file diff --git a/test/jsonchecker/fail33.json b/test/jsonchecker/fail33.json new file mode 100644 index 0000000..ca5eb19 --- /dev/null +++ b/test/jsonchecker/fail33.json @@ -0,0 +1 @@ +["mismatch"} \ No newline at end of file diff --git a/test/jsonchecker/fail4.json b/test/jsonchecker/fail4.json new file mode 100644 index 0000000..9de168b --- /dev/null +++ b/test/jsonchecker/fail4.json @@ -0,0 +1 @@ +["extra comma",] \ No newline at end of file diff --git a/test/jsonchecker/fail5.json b/test/jsonchecker/fail5.json new file mode 100644 index 0000000..ddf3ce3 --- /dev/null +++ b/test/jsonchecker/fail5.json @@ -0,0 +1 @@ +["double extra comma",,] \ No newline at end of file diff --git a/test/jsonchecker/fail6.json b/test/jsonchecker/fail6.json new file mode 100644 index 0000000..ed91580 --- /dev/null +++ b/test/jsonchecker/fail6.json @@ -0,0 +1 @@ +[ , "<-- missing value"] \ No newline at end of file diff --git a/test/jsonchecker/fail7.json b/test/jsonchecker/fail7.json new file mode 100644 index 0000000..8a96af3 --- /dev/null +++ b/test/jsonchecker/fail7.json @@ -0,0 +1 @@ +["Comma after the close"], \ No newline at end of file diff --git a/test/jsonchecker/fail8.json b/test/jsonchecker/fail8.json new file mode 100644 index 0000000..b28479c --- /dev/null +++ b/test/jsonchecker/fail8.json @@ -0,0 +1 @@ +["Extra close"]] \ No newline at end of file diff --git a/test/jsonchecker/fail9.json b/test/jsonchecker/fail9.json new file mode 100644 index 0000000..5815574 --- /dev/null +++ b/test/jsonchecker/fail9.json @@ -0,0 +1 @@ +{"Extra comma": true,} \ No newline at end of file diff --git a/test/jsonchecker/pass1.json b/test/jsonchecker/pass1.json new file mode 100644 index 0000000..70e2685 --- /dev/null +++ b/test/jsonchecker/pass1.json @@ -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": "" \u0022 %22 0x22 034 "", + "\/\\\"\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"] \ No newline at end of file diff --git a/test/jsonchecker/pass2.json b/test/jsonchecker/pass2.json new file mode 100644 index 0000000..d3c63c7 --- /dev/null +++ b/test/jsonchecker/pass2.json @@ -0,0 +1 @@ +[[[[[[[[[[[[[[[[[[["Not too deep"]]]]]]]]]]]]]]]]]]] \ No newline at end of file diff --git a/test/jsonchecker/pass3.json b/test/jsonchecker/pass3.json new file mode 100644 index 0000000..4528d51 --- /dev/null +++ b/test/jsonchecker/pass3.json @@ -0,0 +1,6 @@ +{ + "JSON Test Pattern pass3": { + "The outermost value": "must be an object or array.", + "In this test": "It is an object." + } +} diff --git a/test/jsonchecker/readme.txt b/test/jsonchecker/readme.txt new file mode 100644 index 0000000..0efc2a4 --- /dev/null +++ b/test/jsonchecker/readme.txt @@ -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. diff --git a/test/runjsontests.py b/test/runjsontests.py index 2cf37e2..fa85972 100644 --- a/test/runjsontests.py +++ b/test/runjsontests.py @@ -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' ) ) - test_jsonchecker = glob( os.path.join( input_dir, 'jsonchecker', '*.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: