Skip to content

Commit

Permalink
Added structured error reporting to Reader.
Browse files Browse the repository at this point in the history
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 ([email protected]) for the patch.
  • Loading branch information
jacobsa committed Apr 23, 2014
1 parent 642befc commit 68db655
Show file tree
Hide file tree
Showing 5 changed files with 269 additions and 3 deletions.
21 changes: 21 additions & 0 deletions include/json/reader.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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<StructuredError> getStructuredErrors() const;

private:
enum TokenType
{
Expand Down
12 changes: 12 additions & 0 deletions include/json/value.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand Down Expand Up @@ -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_;
};


Expand Down
43 changes: 41 additions & 2 deletions src/lib_json/json_reader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand All @@ -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_ )
Expand All @@ -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 );
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -963,6 +983,25 @@ Reader::getFormattedErrorMessages() const
}


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;
Expand Down
59 changes: 58 additions & 1 deletion src/lib_json/json_value.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,8 @@ Value::Value( ValueType type )
, itemIsUsed_( 0 )
#endif
, comments_( 0 )
, start_( 0 )
, limit_( 0 )
{
switch ( type )
{
Expand Down Expand Up @@ -318,6 +320,8 @@ Value::Value( UInt value )
, itemIsUsed_( 0 )
#endif
, comments_( 0 )
, start_( 0 )
, limit_( 0 )
{
value_.uint_ = value;
}
Expand All @@ -329,6 +333,8 @@ Value::Value( Int value )
, itemIsUsed_( 0 )
#endif
, comments_( 0 )
, start_( 0 )
, limit_( 0 )
{
value_.int_ = value;
}
Expand All @@ -342,6 +348,8 @@ Value::Value( Int64 value )
, itemIsUsed_( 0 )
#endif
, comments_( 0 )
, start_( 0 )
, limit_( 0 )
{
value_.int_ = value;
}
Expand All @@ -354,6 +362,8 @@ Value::Value( UInt64 value )
, itemIsUsed_( 0 )
#endif
, comments_( 0 )
, start_( 0 )
, limit_( 0 )
{
value_.uint_ = value;
}
Expand All @@ -366,6 +376,8 @@ Value::Value( double value )
, itemIsUsed_( 0 )
#endif
, comments_( 0 )
, start_( 0 )
, limit_( 0 )
{
value_.real_ = value;
}
Expand All @@ -377,6 +389,8 @@ Value::Value( const char *value )
, itemIsUsed_( 0 )
#endif
, comments_( 0 )
, start_( 0 )
, limit_( 0 )
{
value_.string_ = duplicateStringValue( value );
}
Expand All @@ -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) );
Expand All @@ -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() );
Expand All @@ -416,6 +434,8 @@ Value::Value( const StaticString &value )
, itemIsUsed_( 0 )
#endif
, comments_( 0 )
, start_( 0 )
, limit_( 0 )
{
value_.string_ = const_cast<char *>( value.c_str() );
}
Expand All @@ -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() );
}
Expand All @@ -441,6 +463,8 @@ Value::Value( bool value )
, itemIsUsed_( 0 )
#endif
, comments_( 0 )
, start_( 0 )
, limit_( 0 )
{
value_.bool_ = value;
}
Expand All @@ -453,6 +477,8 @@ Value::Value( const Value &other )
, itemIsUsed_( 0 )
#endif
, comments_( 0 )
, start_( other.start_ )
, limit_( other.limit_ )
{
switch ( type_ )
{
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
{
Expand Down
Loading

0 comments on commit 68db655

Please sign in to comment.