Added structured error reporting to Reader.
This allows applications for interactively viewing or editing JSON to do
a better job of highlighting errors. Also added offset accessors to
Value, offering the same sort of functionality even for non-errors.
Thanks to Zach Clifford (zacharyc@google.com) for the patch.
git-svn-id: http://jsoncpp.svn.sourceforge.net/svnroot/jsoncpp/trunk@281 1f120ed1-78a5-a849-adca-83f0a9e25bb6
diff --git a/jsoncpp/include/json/reader.h b/jsoncpp/include/json/reader.h
index 0771342..6271f71 100644
--- a/jsoncpp/include/json/reader.h
+++ b/jsoncpp/include/json/reader.h
@@ -33,6 +33,19 @@
typedef char Char;
typedef const Char *Location;
+ /** \brief An error tagged with where in the JSON text it was encountered.
+ *
+ * The offsets give the [start, limit) range of bytes within the text. Note
+ * that this is bytes, not codepoints.
+ *
+ */
+ struct StructuredError
+ {
+ size_t offset_start;
+ size_t offset_limit;
+ std::string message;
+ };
+
/** \brief Constructs a Reader allowing all features
* for parsing.
*/
@@ -95,6 +108,14 @@
*/
std::string getFormattedErrorMessages() const;
+ /** \brief Returns a vector of structured erros encounted while parsing.
+ * \return A (possibly empty) vector of StructuredError objects. Currently
+ * only one error can be returned, but the caller should tolerate multiple
+ * errors. This can occur if the parser recovers from a non-fatal
+ * parse error and then encounters additional errors.
+ */
+ std::vector<StructuredError> getStructuredErrors() const;
+
private:
enum TokenType
{
diff --git a/jsoncpp/include/json/value.h b/jsoncpp/include/json/value.h
index bd7f181..f18457a 100644
--- a/jsoncpp/include/json/value.h
+++ b/jsoncpp/include/json/value.h
@@ -442,6 +442,13 @@
iterator begin();
iterator end();
+ // Accessors for the [start, limit) range of bytes within the JSON text from
+ // which this value was parsed, if any.
+ void setOffsetStart( size_t start );
+ void setOffsetLimit( size_t limit );
+ size_t getOffsetStart() const;
+ size_t getOffsetLimit() const;
+
private:
Value &resolveReference( const char *key,
bool isStatic );
@@ -509,6 +516,11 @@
int memberNameIsStatic_ : 1; // used by the ValueInternalMap container.
# endif
CommentInfo *comments_;
+
+ // [start, limit) byte offsets in the source JSON text from which this Value
+ // was extracted.
+ size_t start_;
+ size_t limit_;
};
diff --git a/jsoncpp/src/lib_json/json_reader.cpp b/jsoncpp/src/lib_json/json_reader.cpp
index 9ba4024..4f592cd 100644
--- a/jsoncpp/src/lib_json/json_reader.cpp
+++ b/jsoncpp/src/lib_json/json_reader.cpp
@@ -215,9 +215,11 @@
{
case tokenObjectBegin:
successful = readObject( token );
+ currentValue().setOffsetLimit(current_ - begin_);
break;
case tokenArrayBegin:
successful = readArray( token );
+ currentValue().setOffsetLimit(current_ - begin_);
break;
case tokenNumber:
successful = decodeNumber( token );
@@ -227,12 +229,18 @@
break;
case tokenTrue:
currentValue() = true;
+ currentValue().setOffsetStart(token.start_ - begin_);
+ currentValue().setOffsetLimit(token.end_ - begin_);
break;
case tokenFalse:
currentValue() = false;
+ currentValue().setOffsetStart(token.start_ - begin_);
+ currentValue().setOffsetLimit(token.end_ - begin_);
break;
case tokenNull:
currentValue() = Value();
+ currentValue().setOffsetStart(token.start_ - begin_);
+ currentValue().setOffsetLimit(token.end_ - begin_);
break;
case tokenArraySeparator:
if ( features_.allowDroppedNullPlaceholders_ )
@@ -241,10 +249,14 @@
// token.
current_--;
currentValue() = Value();
+ currentValue().setOffsetStart(current_ - begin_ - 1);
+ currentValue().setOffsetLimit(current_ - begin_);
break;
}
// Else, fall through...
default:
+ currentValue().setOffsetStart(token.start_ - begin_);
+ currentValue().setOffsetLimit(token.end_ - begin_);
return addError( "Syntax error: value, object or array expected.", token );
}
@@ -493,11 +505,12 @@
bool
-Reader::readObject( Token &/*tokenStart*/ )
+Reader::readObject( Token &tokenStart )
{
Token tokenName;
std::string name;
currentValue() = Value( objectValue );
+ currentValue().setOffsetStart(tokenStart.start_ - begin_);
while ( readToken( tokenName ) )
{
bool initialTokenOk = true;
@@ -564,9 +577,10 @@
bool
-Reader::readArray( Token &/*tokenStart*/ )
+Reader::readArray( Token &tokenStart )
{
currentValue() = Value( arrayValue );
+ currentValue().setOffsetStart(tokenStart.start_ - begin_);
skipSpaces();
if ( *current_ == ']' ) // empty array
{
@@ -613,6 +627,8 @@
if ( !decodeNumber( token, decoded ) )
return false;
currentValue() = decoded;
+ currentValue().setOffsetStart(token.start_ - begin_);
+ currentValue().setOffsetLimit(token.end_ - begin_);
return true;
}
@@ -678,6 +694,8 @@
if ( !decodeDouble( token, decoded ) )
return false;
currentValue() = decoded;
+ currentValue().setOffsetStart(token.start_ - begin_);
+ currentValue().setOffsetLimit(token.end_ - begin_);
return true;
}
@@ -729,6 +747,8 @@
if ( !decodeString( token, decoded ) )
return false;
currentValue() = decoded;
+ currentValue().setOffsetStart(token.start_ - begin_);
+ currentValue().setOffsetLimit(token.end_ - begin_);
return true;
}
@@ -963,6 +983,25 @@
}
+std::vector<Reader::StructuredError>
+Reader::getStructuredErrors() const
+{
+ std::vector<Reader::StructuredError> allErrors;
+ for ( Errors::const_iterator itError = errors_.begin();
+ itError != errors_.end();
+ ++itError )
+ {
+ const ErrorInfo &error = *itError;
+ Reader::StructuredError structured;
+ structured.offset_start = error.token_.start_ - begin_;
+ structured.offset_limit = error.token_.end_ - begin_;
+ structured.message = error.message_;
+ allErrors.push_back(structured);
+ }
+ return allErrors;
+}
+
+
std::istream& operator>>( std::istream &sin, Value &root )
{
Json::Reader reader;
diff --git a/jsoncpp/src/lib_json/json_value.cpp b/jsoncpp/src/lib_json/json_value.cpp
index dd8dda0..abc59c8 100644
--- a/jsoncpp/src/lib_json/json_value.cpp
+++ b/jsoncpp/src/lib_json/json_value.cpp
@@ -274,6 +274,8 @@
, itemIsUsed_( 0 )
#endif
, comments_( 0 )
+ , start_( 0 )
+ , limit_( 0 )
{
switch ( type )
{
@@ -318,6 +320,8 @@
, itemIsUsed_( 0 )
#endif
, comments_( 0 )
+ , start_( 0 )
+ , limit_( 0 )
{
value_.uint_ = value;
}
@@ -329,6 +333,8 @@
, itemIsUsed_( 0 )
#endif
, comments_( 0 )
+ , start_( 0 )
+ , limit_( 0 )
{
value_.int_ = value;
}
@@ -342,6 +348,8 @@
, itemIsUsed_( 0 )
#endif
, comments_( 0 )
+ , start_( 0 )
+ , limit_( 0 )
{
value_.int_ = value;
}
@@ -354,6 +362,8 @@
, itemIsUsed_( 0 )
#endif
, comments_( 0 )
+ , start_( 0 )
+ , limit_( 0 )
{
value_.uint_ = value;
}
@@ -366,6 +376,8 @@
, itemIsUsed_( 0 )
#endif
, comments_( 0 )
+ , start_( 0 )
+ , limit_( 0 )
{
value_.real_ = value;
}
@@ -377,6 +389,8 @@
, itemIsUsed_( 0 )
#endif
, comments_( 0 )
+ , start_( 0 )
+ , limit_( 0 )
{
value_.string_ = duplicateStringValue( value );
}
@@ -390,6 +404,8 @@
, itemIsUsed_( 0 )
#endif
, comments_( 0 )
+ , start_( 0 )
+ , limit_( 0 )
{
value_.string_ = duplicateStringValue( beginValue,
(unsigned int)(endValue - beginValue) );
@@ -403,6 +419,8 @@
, itemIsUsed_( 0 )
#endif
, comments_( 0 )
+ , start_( 0 )
+ , limit_( 0 )
{
value_.string_ = duplicateStringValue( value.c_str(),
(unsigned int)value.length() );
@@ -416,6 +434,8 @@
, itemIsUsed_( 0 )
#endif
, comments_( 0 )
+ , start_( 0 )
+ , limit_( 0 )
{
value_.string_ = const_cast<char *>( value.c_str() );
}
@@ -429,6 +449,8 @@
, itemIsUsed_( 0 )
#endif
, comments_( 0 )
+ , start_( 0 )
+ , limit_( 0 )
{
value_.string_ = duplicateStringValue( value, value.length() );
}
@@ -441,6 +463,8 @@
, itemIsUsed_( 0 )
#endif
, comments_( 0 )
+ , start_( 0 )
+ , limit_( 0 )
{
value_.bool_ = value;
}
@@ -453,6 +477,8 @@
, itemIsUsed_( 0 )
#endif
, comments_( 0 )
+ , start_( other.start_ )
+ , limit_( other.limit_ )
{
switch ( type_ )
{
@@ -557,6 +583,8 @@
int temp2 = allocated_;
allocated_ = other.allocated_;
other.allocated_ = temp2;
+ std::swap( start_, other.start_ );
+ std::swap( limit_, other.limit_ );
}
ValueType
@@ -1027,7 +1055,8 @@
Value::clear()
{
JSON_ASSERT_MESSAGE( type_ == nullValue || type_ == arrayValue || type_ == objectValue, "in Json::Value::clear(): requires complex value" );
-
+ start_ = 0;
+ limit_ = 0;
switch ( type_ )
{
#ifndef JSON_VALUE_USE_INTERNAL_MAP
@@ -1556,6 +1585,34 @@
}
+void
+Value::setOffsetStart( size_t start )
+{
+ start_ = start;
+}
+
+
+void
+Value::setOffsetLimit( size_t limit )
+{
+ limit_ = limit;
+}
+
+
+size_t
+Value::getOffsetStart() const
+{
+ return start_;
+}
+
+
+size_t
+Value::getOffsetLimit() const
+{
+ return limit_;
+}
+
+
std::string
Value::toStyledString() const
{
diff --git a/jsoncpp/src/test_lib_json/main.cpp b/jsoncpp/src/test_lib_json/main.cpp
index dbcbd78..f82a384 100644
--- a/jsoncpp/src/test_lib_json/main.cpp
+++ b/jsoncpp/src/test_lib_json/main.cpp
@@ -1474,6 +1474,26 @@
}
+JSONTEST_FIXTURE( ValueTest, offsetAccessors )
+{
+ Json::Value x;
+ JSONTEST_ASSERT( x.getOffsetStart() == 0 );
+ JSONTEST_ASSERT( x.getOffsetLimit() == 0 );
+ x.setOffsetStart(10);
+ x.setOffsetLimit(20);
+ JSONTEST_ASSERT( x.getOffsetStart() == 10 );
+ JSONTEST_ASSERT( x.getOffsetLimit() == 20 );
+ Json::Value y(x);
+ JSONTEST_ASSERT( y.getOffsetStart() == 10 );
+ JSONTEST_ASSERT( y.getOffsetLimit() == 20 );
+ Json::Value z;
+ z.swap(y);
+ JSONTEST_ASSERT( z.getOffsetStart() == 10 );
+ JSONTEST_ASSERT( z.getOffsetLimit() == 20 );
+ JSONTEST_ASSERT( y.getOffsetStart() == 0 );
+ JSONTEST_ASSERT( y.getOffsetLimit() == 0 );
+}
+
struct WriterTest : JsonTest::TestCase
{
};
@@ -1490,6 +1510,115 @@
}
+struct ReaderTest : JsonTest::TestCase
+{
+};
+
+
+JSONTEST_FIXTURE( ReaderTest, parseWithNoErrors )
+{
+ Json::Reader reader;
+ Json::Value root;
+ bool ok = reader.parse(
+ "{ \"property\" : \"value\" }",
+ root);
+ JSONTEST_ASSERT( ok );
+ JSONTEST_ASSERT( reader.getFormattedErrorMessages().size() == 0 );
+ JSONTEST_ASSERT( reader.getStructuredErrors().size() == 0 );
+}
+
+
+JSONTEST_FIXTURE( ReaderTest, parseWithNoErrorsTestingOffsets )
+{
+ Json::Reader reader;
+ Json::Value root;
+ bool ok = reader.parse(
+ "{ \"property\" : [\"value\", \"value2\"], \"obj\" : { \"nested\" : 123, \"bool\" : true}, \"null\" : null, \"false\" : false }",
+ root);
+ JSONTEST_ASSERT( ok );
+ JSONTEST_ASSERT( reader.getFormattedErrorMessages().size() == 0 );
+ JSONTEST_ASSERT( reader.getStructuredErrors().size() == 0 );
+ JSONTEST_ASSERT( root["property"].getOffsetStart() == 15 );
+ JSONTEST_ASSERT( root["property"].getOffsetLimit() == 34 );
+ JSONTEST_ASSERT( root["property"][0].getOffsetStart() == 16 );
+ JSONTEST_ASSERT( root["property"][0].getOffsetLimit() == 23 );
+ JSONTEST_ASSERT( root["property"][1].getOffsetStart() == 25 );
+ JSONTEST_ASSERT( root["property"][1].getOffsetLimit() == 33 );
+ JSONTEST_ASSERT( root["obj"].getOffsetStart() == 44 );
+ JSONTEST_ASSERT( root["obj"].getOffsetLimit() == 76 );
+ JSONTEST_ASSERT( root["obj"]["nested"].getOffsetStart() == 57 );
+ JSONTEST_ASSERT( root["obj"]["nested"].getOffsetLimit() == 60 );
+ JSONTEST_ASSERT( root["obj"]["bool"].getOffsetStart() == 71 );
+ JSONTEST_ASSERT( root["obj"]["bool"].getOffsetLimit() == 75 );
+ JSONTEST_ASSERT( root["null"].getOffsetStart() == 87 );
+ JSONTEST_ASSERT( root["null"].getOffsetLimit() == 91 );
+ JSONTEST_ASSERT( root["false"].getOffsetStart() == 103 );
+ JSONTEST_ASSERT( root["false"].getOffsetLimit() == 108 );
+ JSONTEST_ASSERT( root.getOffsetStart() == 0 );
+ JSONTEST_ASSERT( root.getOffsetLimit() == 110 );
+}
+
+
+JSONTEST_FIXTURE( ReaderTest, parseWithOneError )
+{
+ Json::Reader reader;
+ Json::Value root;
+ bool ok = reader.parse(
+ "{ \"property\" :: \"value\" }",
+ root);
+ JSONTEST_ASSERT( !ok );
+ JSONTEST_ASSERT( reader.getFormattedErrorMessages() ==
+ "* Line 1, Column 15\n Syntax error: value, object or array expected.\n" );
+ std::vector<Json::Reader::StructuredError> errors =
+ reader.getStructuredErrors();
+ JSONTEST_ASSERT( errors.size() == 1 );
+ JSONTEST_ASSERT( errors.at(0).offset_start == 14 );
+ JSONTEST_ASSERT( errors.at(0).offset_limit == 15 );
+ JSONTEST_ASSERT( errors.at(0).message ==
+ "Syntax error: value, object or array expected." );
+}
+
+
+JSONTEST_FIXTURE( ReaderTest, parseChineseWithOneError )
+{
+ Json::Reader reader;
+ Json::Value root;
+ bool ok = reader.parse(
+ "{ \"pr佐藤erty\" :: \"value\" }",
+ root);
+ JSONTEST_ASSERT( !ok );
+ JSONTEST_ASSERT( reader.getFormattedErrorMessages() ==
+ "* Line 1, Column 19\n Syntax error: value, object or array expected.\n" );
+ std::vector<Json::Reader::StructuredError> errors =
+ reader.getStructuredErrors();
+ JSONTEST_ASSERT( errors.size() == 1 );
+ JSONTEST_ASSERT( errors.at(0).offset_start == 18 );
+ JSONTEST_ASSERT( errors.at(0).offset_limit == 19 );
+ JSONTEST_ASSERT( errors.at(0).message ==
+ "Syntax error: value, object or array expected." );
+}
+
+
+JSONTEST_FIXTURE( ReaderTest, parseWithDetailError )
+{
+ Json::Reader reader;
+ Json::Value root;
+ bool ok = reader.parse(
+ "{ \"property\" : \"v\\alue\" }",
+ root);
+ JSONTEST_ASSERT( !ok );
+ JSONTEST_ASSERT( reader.getFormattedErrorMessages() ==
+ "* Line 1, Column 16\n Bad escape sequence in string\nSee Line 1, Column 20 for detail.\n" );
+ std::vector<Json::Reader::StructuredError> errors =
+ reader.getStructuredErrors();
+ JSONTEST_ASSERT( errors.size() == 1 );
+ JSONTEST_ASSERT( errors.at(0).offset_start == 15 );
+ JSONTEST_ASSERT( errors.at(0).offset_limit == 23 );
+ JSONTEST_ASSERT( errors.at(0).message ==
+ "Bad escape sequence in string" );
+}
+
+
int main( int argc, const char *argv[] )
{
JsonTest::Runner runner;
@@ -1512,6 +1641,14 @@
JSONTEST_REGISTER_FIXTURE( runner, ValueTest, compareObject );
JSONTEST_REGISTER_FIXTURE( runner, ValueTest, compareType );
JSONTEST_REGISTER_FIXTURE( runner, ValueTest, checkInteger );
+ JSONTEST_REGISTER_FIXTURE( runner, ValueTest, offsetAccessors );
+
+ JSONTEST_REGISTER_FIXTURE( runner, ReaderTest, parseWithNoErrors );
+ JSONTEST_REGISTER_FIXTURE( runner, ReaderTest, parseWithNoErrorsTestingOffsets );
+ JSONTEST_REGISTER_FIXTURE( runner, ReaderTest, parseWithOneError );
+ JSONTEST_REGISTER_FIXTURE( runner, ReaderTest, parseChineseWithOneError );
+ JSONTEST_REGISTER_FIXTURE( runner, ReaderTest, parseWithDetailError );
+
JSONTEST_REGISTER_FIXTURE( runner, WriterTest, dropNullPlaceholders );
return runner.runCommandLine( argc, argv );
}