Skip to content

Commit 8868147

Browse files
committed
- 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
1 parent 64e07e5 commit 8868147

46 files changed

Lines changed: 307 additions & 27 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,6 @@ and TARGET may be:
4242
doc: build documentation
4343
doc-dist: build documentation tarball
4444

45+
To run the test manually:
46+
cd test
47+
python runjsontests.py "path to jsontest.exe"

include/json/features.h

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#ifndef CPPTL_JSON_FEATURES_H_INCLUDED
2+
# define CPPTL_JSON_FEATURES_H_INCLUDED
3+
4+
# include "forwards.h"
5+
6+
namespace Json {
7+
8+
/** \brief Configuration passed to reader and writer.
9+
* This configuration object can be used to force the Reader or Writer
10+
* to behave in a standard conforming way.
11+
*/
12+
class JSON_API Features
13+
{
14+
public:
15+
/** \brief A configuration that allows all features and assumes all strings are UTF-8.
16+
* - C & C++ comments are allowed
17+
* - Root object can be any JSON value
18+
* - Assumes Value strings are encoded in UTF-8
19+
*/
20+
static Features all();
21+
22+
/** \brief A configuration that is strictly compatible with the JSON specification.
23+
* - Comments are forbidden.
24+
* - Root object must be either an array or an object value.
25+
* - Assumes Value strings are encoded in UTF-8
26+
*/
27+
static Features strictMode();
28+
29+
/** \brief Initialize the configuration like JsonConfig::allFeatures;
30+
*/
31+
Features();
32+
33+
/// \c true if comments are allowed. Default: \c true.
34+
bool allowComments_;
35+
36+
/// \c true if root must be either an array or an object value. Default: \c false.
37+
bool strictRoot_;
38+
};
39+
40+
} // namespace Json
41+
42+
#endif // CPPTL_JSON_FEATURES_H_INCLUDED

include/json/forwards.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ namespace Json {
99
class Reader;
1010
class StyledWriter;
1111

12+
// features.h
13+
class Features;
14+
1215
// value.h
1316
class StaticString;
1417
class Path;

include/json/json.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
# include "value.h"
66
# include "reader.h"
77
# include "writer.h"
8+
# include "features.h"
89

910
#endif // JSON_JSON_H_INCLUDED

include/json/reader.h

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#ifndef CPPTL_JSON_READER_H_INCLUDED
22
# define CPPTL_JSON_READER_H_INCLUDED
33

4-
# include "forwards.h"
4+
# include "features.h"
55
# include "value.h"
66
# include <deque>
77
# include <stack>
@@ -10,10 +10,7 @@
1010

1111
namespace Json {
1212

13-
class Value;
14-
1513
/** \brief Unserialize a <a HREF="http://www.json.org">JSON</a> document into a Value.
16-
*
1714
*
1815
*/
1916
class JSON_API Reader
@@ -22,14 +19,24 @@ namespace Json {
2219
typedef char Char;
2320
typedef const Char *Location;
2421

22+
/** \brief Constructs a Reader allowing all features
23+
* for parsing.
24+
*/
2525
Reader();
2626

27+
/** \brief Constructs a Reader allowing the specified feature set
28+
* for parsing.
29+
*/
30+
Reader( const Features &features );
31+
2732
/** \brief Read a Value from a <a HREF="http://www.json.org">JSON</a> document.
2833
* \param document UTF-8 encoded string containing the document to read.
2934
* \param root [out] Contains the root value of the document if it was
3035
* successfully parsed.
3136
* \param collectComments \c true to collect comment and allow writing them back during
3237
* serialization, \c false to discard comments.
38+
* This parameter is ignored if Features::allowComments_
39+
* is \c false.
3340
* \return \c true if the document was successfully parsed, \c false if an error occurred.
3441
*/
3542
bool parse( const std::string &document,
@@ -42,6 +49,8 @@ namespace Json {
4249
* successfully parsed.
4350
* \param collectComments \c true to collect comment and allow writing them back during
4451
* serialization, \c false to discard comments.
52+
* This parameter is ignored if Features::allowComments_
53+
* is \c false.
4554
* \return \c true if the document was successfully parsed, \c false if an error occurred.
4655
*/
4756
bool parse( const char *beginDoc, const char *endDoc,
@@ -50,7 +59,7 @@ namespace Json {
5059

5160
/// \brief Parse from input stream.
5261
/// \see Json::operator>>(std::istream&, Json::Value&).
53-
bool parse( std::istream&,
62+
bool parse( std::istream &is,
5463
Value &root,
5564
bool collectComments = true );
5665

@@ -152,6 +161,7 @@ namespace Json {
152161
Location lastValueEnd_;
153162
Value *lastValue_;
154163
std::string commentsBefore_;
164+
Features features_;
155165
bool collectComments_;
156166
};
157167

src/jsontestrunner/main.cpp

Lines changed: 64 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,11 @@ static int
8686
parseAndSaveValueTree( const std::string &input,
8787
const std::string &actual,
8888
const std::string &kind,
89-
Json::Value &root )
89+
Json::Value &root,
90+
const Json::Features &features,
91+
bool parseOnly )
9092
{
91-
Json::Reader reader;
93+
Json::Reader reader( features );
9294
bool parsingSuccessful = reader.parse( input, root );
9395
if ( !parsingSuccessful )
9496
{
@@ -98,14 +100,17 @@ parseAndSaveValueTree( const std::string &input,
98100
return 1;
99101
}
100102

101-
FILE *factual = fopen( actual.c_str(), "wt" );
102-
if ( !factual )
103+
if ( !parseOnly )
103104
{
104-
printf( "Failed to create %s actual file.\n", kind.c_str() );
105-
return 2;
105+
FILE *factual = fopen( actual.c_str(), "wt" );
106+
if ( !factual )
107+
{
108+
printf( "Failed to create %s actual file.\n", kind.c_str() );
109+
return 2;
110+
}
111+
printValueTree( factual, root );
112+
fclose( factual );
106113
}
107-
printValueTree( factual, root );
108-
fclose( factual );
109114
return 0;
110115
}
111116

@@ -143,25 +148,65 @@ removeSuffix( const std::string &path,
143148
return path.substr( 0, path.length() - extension.length() );
144149
}
145150

151+
static int
152+
printUsage( const char *argv[] )
153+
{
154+
printf( "Usage: %s [--strict] input-json-file", argv[0] );
155+
return 3;
156+
}
157+
158+
159+
int
160+
parseCommandLine( int argc, const char *argv[],
161+
Json::Features &features, std::string &path,
162+
bool &parseOnly )
163+
{
164+
parseOnly = false;
165+
if ( argc < 2 )
166+
{
167+
return printUsage( argv );
168+
}
169+
170+
int index = 1;
171+
if ( std::string(argv[1]) == "--json-checker" )
172+
{
173+
features = Json::Features::strictMode();
174+
parseOnly = true;
175+
++index;
176+
}
177+
178+
if ( index == argc || index + 1 < argc )
179+
{
180+
return printUsage( argv );
181+
}
182+
183+
path = argv[index];
184+
return 0;
185+
}
186+
187+
146188
int main( int argc, const char *argv[] )
147189
{
148-
if ( argc != 2 )
190+
std::string path;
191+
Json::Features features;
192+
bool parseOnly;
193+
int exitCode = parseCommandLine( argc, argv, features, path, parseOnly );
194+
if ( exitCode != 0 )
149195
{
150-
printf( "Usage: %s input-json-file", argv[0] );
151-
return 3;
196+
return exitCode;
152197
}
153198

154-
std::string input = readInputTestFile( argv[1] );
199+
std::string input = readInputTestFile( path.c_str() );
155200
if ( input.empty() )
156201
{
157-
printf( "Failed to read input or empty input: %s\n", argv[1] );
202+
printf( "Failed to read input or empty input: %s\n", path.c_str() );
158203
return 3;
159204
}
160205

161206
std::string basePath = removeSuffix( argv[1], ".json" );
162-
if ( basePath.empty() )
207+
if ( !parseOnly && basePath.empty() )
163208
{
164-
printf( "Bad input path. Path does not end with '.expected':\n%s\n", argv[1] );
209+
printf( "Bad input path. Path does not end with '.expected':\n%s\n", path.c_str() );
165210
return 3;
166211
}
167212

@@ -170,15 +215,16 @@ int main( int argc, const char *argv[] )
170215
std::string rewriteActualPath = basePath + ".actual-rewrite";
171216

172217
Json::Value root;
173-
int exitCode = parseAndSaveValueTree( input, actualPath, "input", root );
174-
if ( exitCode == 0 )
218+
exitCode = parseAndSaveValueTree( input, actualPath, "input", root, features, parseOnly );
219+
if ( exitCode == 0 && !parseOnly )
175220
{
176221
std::string rewrite;
177222
exitCode = rewriteValueTree( rewritePath, root, rewrite );
178223
if ( exitCode == 0 )
179224
{
180225
Json::Value rewriteRoot;
181-
exitCode = parseAndSaveValueTree( rewrite, rewriteActualPath, "rewrite", rewriteRoot );
226+
exitCode = parseAndSaveValueTree( rewrite, rewriteActualPath,
227+
"rewrite", rewriteRoot, features, parseOnly );
182228
}
183229
}
184230

src/lib_json/json_reader.cpp

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,36 @@
1313

1414
namespace Json {
1515

16+
// Implementation of class Features
17+
// ////////////////////////////////
18+
19+
Features::Features()
20+
: allowComments_( true )
21+
, strictRoot_( false )
22+
{
23+
}
24+
25+
26+
Features
27+
Features::all()
28+
{
29+
return Features();
30+
}
31+
32+
33+
Features
34+
Features::strictMode()
35+
{
36+
Features features;
37+
features.allowComments_ = false;
38+
features.strictRoot_ = true;
39+
return features;
40+
}
41+
42+
// Implementation of class Reader
43+
// ////////////////////////////////
44+
45+
1646
static inline bool
1747
in( Reader::Char c, Reader::Char c1, Reader::Char c2, Reader::Char c3, Reader::Char c4 )
1848
{
@@ -77,9 +107,17 @@ static std::string codePointToUTF8(unsigned int cp)
77107
// //////////////////////////////////////////////////////////////////
78108

79109
Reader::Reader()
110+
: features_( Features::all() )
80111
{
81112
}
82113

114+
115+
Reader::Reader( const Features &features )
116+
: features_( features )
117+
{
118+
}
119+
120+
83121
bool
84122
Reader::parse( const std::string &document,
85123
Value &root,
@@ -91,6 +129,7 @@ Reader::parse( const std::string &document,
91129
return parse( begin, end, root, collectComments );
92130
}
93131

132+
94133
bool
95134
Reader::parse( std::istream& sin,
96135
Value &root,
@@ -113,6 +152,11 @@ Reader::parse( const char *beginDoc, const char *endDoc,
113152
Value &root,
114153
bool collectComments )
115154
{
155+
if ( !features_.allowComments_ )
156+
{
157+
collectComments = false;
158+
}
159+
116160
begin_ = beginDoc;
117161
end_ = endDoc;
118162
collectComments_ = collectComments;
@@ -130,6 +174,19 @@ Reader::parse( const char *beginDoc, const char *endDoc,
130174
skipCommentTokens( token );
131175
if ( collectComments_ && !commentsBefore_.empty() )
132176
root.setComment( commentsBefore_, commentAfter );
177+
if ( features_.strictRoot_ )
178+
{
179+
if ( !root.isArray() && !root.isObject() )
180+
{
181+
// Set error location to start of doc, ideally should be first token found in doc
182+
token.type_ = tokenError;
183+
token.start_ = beginDoc;
184+
token.end_ = endDoc;
185+
addError( "A valid JSON document must be either an array or an object value.",
186+
token );
187+
return false;
188+
}
189+
}
133190
return successful;
134191
}
135192

@@ -188,11 +245,18 @@ Reader::readValue()
188245
void
189246
Reader::skipCommentTokens( Token &token )
190247
{
191-
do
248+
if ( features_.allowComments_ )
249+
{
250+
do
251+
{
252+
readToken( token );
253+
}
254+
while ( token.type_ == tokenComment );
255+
}
256+
else
192257
{
193258
readToken( token );
194259
}
195-
while ( token.type_ == tokenComment );
196260
}
197261

198262

src/lib_json/lib_json.vcproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,9 @@
169169
<File
170170
RelativePath="..\..\include\json\config.h">
171171
</File>
172+
<File
173+
RelativePath="..\..\include\json\features.h">
174+
</File>
172175
<File
173176
RelativePath="..\..\include\json\forwards.h">
174177
</File>

test/jsonchecker/fail1.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"A JSON payload should be an object or array, not a string."

0 commit comments

Comments
 (0)