diff --git a/include/json/reader.h b/include/json/reader.h index 0771342..6271f71 100644 --- a/include/json/reader.h +++ b/include/json/reader.h @@ -33,6 +33,19 @@ namespace Json { 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 @@ namespace Json { */ 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 getStructuredErrors() const; + private: enum TokenType { diff --git a/include/json/value.h b/include/json/value.h index bd7f181..f18457a 100644 --- a/include/json/value.h +++ b/include/json/value.h @@ -442,6 +442,13 @@ namespace Json { 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 @@ namespace Json { 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/src/lib_json/json_reader.cpp b/src/lib_json/json_reader.cpp index 9ba4024..4f592cd 100644 --- a/src/lib_json/json_reader.cpp +++ b/src/lib_json/json_reader.cpp @@ -215,9 +215,11 @@ Reader::readValue() { 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 @@ Reader::readValue() 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 @@ Reader::readValue() // 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 @@ Reader::readString() 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 @@ Reader::readObject( Token &/*tokenStart*/ ) 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 @@ Reader::decodeNumber( Token &token ) if ( !decodeNumber( token, decoded ) ) return false; currentValue() = decoded; + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); return true; } @@ -678,6 +694,8 @@ Reader::decodeDouble( Token &token ) if ( !decodeDouble( token, decoded ) ) return false; currentValue() = decoded; + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); return true; } @@ -729,6 +747,8 @@ Reader::decodeString( Token &token ) if ( !decodeString( token, decoded ) ) return false; currentValue() = decoded; + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); return true; } @@ -963,6 +983,25 @@ Reader::getFormattedErrorMessages() const } +std::vector +Reader::getStructuredErrors() const +{ + std::vector 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/src/lib_json/json_value.cpp b/src/lib_json/json_value.cpp index dd8dda0..abc59c8 100644 --- a/src/lib_json/json_value.cpp +++ b/src/lib_json/json_value.cpp @@ -274,6 +274,8 @@ Value::Value( ValueType type ) , itemIsUsed_( 0 ) #endif , comments_( 0 ) + , start_( 0 ) + , limit_( 0 ) { switch ( type ) { @@ -318,6 +320,8 @@ Value::Value( UInt value ) , itemIsUsed_( 0 ) #endif , comments_( 0 ) + , start_( 0 ) + , limit_( 0 ) { value_.uint_ = value; } @@ -329,6 +333,8 @@ Value::Value( Int value ) , itemIsUsed_( 0 ) #endif , comments_( 0 ) + , start_( 0 ) + , limit_( 0 ) { value_.int_ = value; } @@ -342,6 +348,8 @@ Value::Value( Int64 value ) , itemIsUsed_( 0 ) #endif , comments_( 0 ) + , start_( 0 ) + , limit_( 0 ) { value_.int_ = value; } @@ -354,6 +362,8 @@ Value::Value( UInt64 value ) , itemIsUsed_( 0 ) #endif , comments_( 0 ) + , start_( 0 ) + , limit_( 0 ) { value_.uint_ = value; } @@ -366,6 +376,8 @@ Value::Value( double value ) , itemIsUsed_( 0 ) #endif , comments_( 0 ) + , start_( 0 ) + , limit_( 0 ) { value_.real_ = value; } @@ -377,6 +389,8 @@ Value::Value( const char *value ) , itemIsUsed_( 0 ) #endif , comments_( 0 ) + , start_( 0 ) + , limit_( 0 ) { value_.string_ = duplicateStringValue( value ); } @@ -390,6 +404,8 @@ Value::Value( const char *beginValue, , itemIsUsed_( 0 ) #endif , comments_( 0 ) + , start_( 0 ) + , limit_( 0 ) { value_.string_ = duplicateStringValue( beginValue, (unsigned int)(endValue - beginValue) ); @@ -403,6 +419,8 @@ Value::Value( const std::string &value ) , itemIsUsed_( 0 ) #endif , comments_( 0 ) + , start_( 0 ) + , limit_( 0 ) { value_.string_ = duplicateStringValue( value.c_str(), (unsigned int)value.length() ); @@ -416,6 +434,8 @@ Value::Value( const StaticString &value ) , itemIsUsed_( 0 ) #endif , comments_( 0 ) + , start_( 0 ) + , limit_( 0 ) { value_.string_ = const_cast( value.c_str() ); } @@ -429,6 +449,8 @@ Value::Value( const CppTL::ConstString &value ) , itemIsUsed_( 0 ) #endif , comments_( 0 ) + , start_( 0 ) + , limit_( 0 ) { value_.string_ = duplicateStringValue( value, value.length() ); } @@ -441,6 +463,8 @@ Value::Value( bool value ) , itemIsUsed_( 0 ) #endif , comments_( 0 ) + , start_( 0 ) + , limit_( 0 ) { value_.bool_ = value; } @@ -453,6 +477,8 @@ Value::Value( const Value &other ) , itemIsUsed_( 0 ) #endif , comments_( 0 ) + , start_( other.start_ ) + , limit_( other.limit_ ) { switch ( type_ ) { @@ -557,6 +583,8 @@ Value::swap( Value &other ) int temp2 = allocated_; allocated_ = other.allocated_; other.allocated_ = temp2; + std::swap( start_, other.start_ ); + std::swap( limit_, other.limit_ ); } ValueType @@ -1027,7 +1055,8 @@ void 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 @@ Value::getComment( CommentPlacement placement ) const } +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/src/test_lib_json/main.cpp b/src/test_lib_json/main.cpp index dbcbd78..f82a384 100644 --- a/src/test_lib_json/main.cpp +++ b/src/test_lib_json/main.cpp @@ -1474,6 +1474,26 @@ JSONTEST_FIXTURE( ValueTest, checkInteger ) } +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 @@ JSONTEST_FIXTURE( WriterTest, dropNullPlaceholders ) } +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 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 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 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 @@ int main( int argc, const char *argv[] ) 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 ); }