JsonCpp project page JsonCpp home page

json_reader.cpp
Go to the documentation of this file.
1 // Copyright 2007-2010 Baptiste Lepilleur
2 // Distributed under MIT license, or public domain if desired and
3 // recognized in your jurisdiction.
4 // See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
5 
6 #if !defined(JSON_IS_AMALGAMATION)
7 # include <json/reader.h>
8 # include <json/value.h>
9 # include "json_tool.h"
10 #endif // if !defined(JSON_IS_AMALGAMATION)
11 #include <utility>
12 #include <cstdio>
13 #include <cassert>
14 #include <cstring>
15 #include <iostream>
16 #include <stdexcept>
17 
18 #if _MSC_VER >= 1400 // VC++ 8.0
19 #pragma warning( disable : 4996 ) // disable warning about strdup being deprecated.
20 #endif
21 
22 namespace Json {
23 
24 // Implementation of class Features
25 // ////////////////////////////////
26 
28  : allowComments_( true )
29  , strictRoot_( false )
30 {
31 }
32 
33 
34 Features
36 {
37  return Features();
38 }
39 
40 
41 Features
43 {
44  Features features;
45  features.allowComments_ = false;
46  features.strictRoot_ = true;
47  return features;
48 }
49 
50 // Implementation of class Reader
51 // ////////////////////////////////
52 
53 
54 static inline bool
56 {
57  return c == c1 || c == c2 || c == c3 || c == c4;
58 }
59 
60 static inline bool
62 {
63  return c == c1 || c == c2 || c == c3 || c == c4 || c == c5;
64 }
65 
66 
67 static bool
69  Reader::Location end )
70 {
71  for ( ;begin < end; ++begin )
72  if ( *begin == '\n' || *begin == '\r' )
73  return true;
74  return false;
75 }
76 
77 
78 // Class Reader
79 // //////////////////////////////////////////////////////////////////
80 
82  : features_( Features::all() )
83 {
84 }
85 
86 
87 Reader::Reader( const Features &features )
88  : features_( features )
89 {
90 }
91 
92 
93 bool
94 Reader::parse( const std::string &document,
95  Value &root,
96  bool collectComments )
97 {
98  document_ = document;
99  const char *begin = document_.c_str();
100  const char *end = begin + document_.length();
101  return parse( begin, end, root, collectComments );
102 }
103 
104 
105 bool
106 Reader::parse( std::istream& sin,
107  Value &root,
108  bool collectComments )
109 {
110  //std::istream_iterator<char> begin(sin);
111  //std::istream_iterator<char> end;
112  // Those would allow streamed input from a file, if parse() were a
113  // template function.
114 
115  // Since std::string is reference-counted, this at least does not
116  // create an extra copy.
117  std::string doc;
118  std::getline(sin, doc, (char)EOF);
119  return parse( doc, root, collectComments );
120 }
121 
122 bool
123 Reader::parse( const char *beginDoc, const char *endDoc,
124  Value &root,
125  bool collectComments )
126 {
127  if ( !features_.allowComments_ )
128  {
129  collectComments = false;
130  }
131 
132  begin_ = beginDoc;
133  end_ = endDoc;
134  collectComments_ = collectComments;
135  current_ = begin_;
136  lastValueEnd_ = 0;
137  lastValue_ = 0;
138  commentsBefore_ = "";
139  errors_.clear();
140  while ( !nodes_.empty() )
141  nodes_.pop();
142  nodes_.push( &root );
143 
144  bool successful = readValue();
145  Token token;
146  skipCommentTokens( token );
147  if ( collectComments_ && !commentsBefore_.empty() )
148  root.setComment( commentsBefore_, commentAfter );
149  if ( features_.strictRoot_ )
150  {
151  if ( !root.isArray() && !root.isObject() )
152  {
153  // Set error location to start of doc, ideally should be first token found in doc
154  token.type_ = tokenError;
155  token.start_ = beginDoc;
156  token.end_ = endDoc;
157  addError( "A valid JSON document must be either an array or an object value.",
158  token );
159  return false;
160  }
161  }
162  return successful;
163 }
164 
165 
166 bool
167 Reader::readValue()
168 {
169  Token token;
170  skipCommentTokens( token );
171  bool successful = true;
172 
173  if ( collectComments_ && !commentsBefore_.empty() )
174  {
175  currentValue().setComment( commentsBefore_, commentBefore );
176  commentsBefore_ = "";
177  }
178 
179 
180  switch ( token.type_ )
181  {
182  case tokenObjectBegin:
183  successful = readObject( token );
184  break;
185  case tokenArrayBegin:
186  successful = readArray( token );
187  break;
188  case tokenNumber:
189  successful = decodeNumber( token );
190  break;
191  case tokenString:
192  successful = decodeString( token );
193  break;
194  case tokenTrue:
195  currentValue() = true;
196  break;
197  case tokenFalse:
198  currentValue() = false;
199  break;
200  case tokenNull:
201  currentValue() = Value();
202  break;
203  default:
204  return addError( "Syntax error: value, object or array expected.", token );
205  }
206 
207  if ( collectComments_ )
208  {
209  lastValueEnd_ = current_;
210  lastValue_ = &currentValue();
211  }
212 
213  return successful;
214 }
215 
216 
217 void
218 Reader::skipCommentTokens( Token &token )
219 {
220  if ( features_.allowComments_ )
221  {
222  do
223  {
224  readToken( token );
225  }
226  while ( token.type_ == tokenComment );
227  }
228  else
229  {
230  readToken( token );
231  }
232 }
233 
234 
235 bool
236 Reader::expectToken( TokenType type, Token &token, const char *message )
237 {
238  readToken( token );
239  if ( token.type_ != type )
240  return addError( message, token );
241  return true;
242 }
243 
244 
245 bool
246 Reader::readToken( Token &token )
247 {
248  skipSpaces();
249  token.start_ = current_;
250  Char c = getNextChar();
251  bool ok = true;
252  switch ( c )
253  {
254  case '{':
255  token.type_ = tokenObjectBegin;
256  break;
257  case '}':
258  token.type_ = tokenObjectEnd;
259  break;
260  case '[':
261  token.type_ = tokenArrayBegin;
262  break;
263  case ']':
264  token.type_ = tokenArrayEnd;
265  break;
266  case '"':
267  token.type_ = tokenString;
268  ok = readString();
269  break;
270  case '/':
271  token.type_ = tokenComment;
272  ok = readComment();
273  break;
274  case '0':
275  case '1':
276  case '2':
277  case '3':
278  case '4':
279  case '5':
280  case '6':
281  case '7':
282  case '8':
283  case '9':
284  case '-':
285  token.type_ = tokenNumber;
286  readNumber();
287  break;
288  case 't':
289  token.type_ = tokenTrue;
290  ok = match( "rue", 3 );
291  break;
292  case 'f':
293  token.type_ = tokenFalse;
294  ok = match( "alse", 4 );
295  break;
296  case 'n':
297  token.type_ = tokenNull;
298  ok = match( "ull", 3 );
299  break;
300  case ',':
301  token.type_ = tokenArraySeparator;
302  break;
303  case ':':
304  token.type_ = tokenMemberSeparator;
305  break;
306  case 0:
307  token.type_ = tokenEndOfStream;
308  break;
309  default:
310  ok = false;
311  break;
312  }
313  if ( !ok )
314  token.type_ = tokenError;
315  token.end_ = current_;
316  return true;
317 }
318 
319 
320 void
321 Reader::skipSpaces()
322 {
323  while ( current_ != end_ )
324  {
325  Char c = *current_;
326  if ( c == ' ' || c == '\t' || c == '\r' || c == '\n' )
327  ++current_;
328  else
329  break;
330  }
331 }
332 
333 
334 bool
335 Reader::match( Location pattern,
336  int patternLength )
337 {
338  if ( end_ - current_ < patternLength )
339  return false;
340  int index = patternLength;
341  while ( index-- )
342  if ( current_[index] != pattern[index] )
343  return false;
344  current_ += patternLength;
345  return true;
346 }
347 
348 
349 bool
350 Reader::readComment()
351 {
352  Location commentBegin = current_ - 1;
353  Char c = getNextChar();
354  bool successful = false;
355  if ( c == '*' )
356  successful = readCStyleComment();
357  else if ( c == '/' )
358  successful = readCppStyleComment();
359  if ( !successful )
360  return false;
361 
362  if ( collectComments_ )
363  {
364  CommentPlacement placement = commentBefore;
365  if ( lastValueEnd_ && !containsNewLine( lastValueEnd_, commentBegin ) )
366  {
367  if ( c != '*' || !containsNewLine( commentBegin, current_ ) )
368  placement = commentAfterOnSameLine;
369  }
370 
371  addComment( commentBegin, current_, placement );
372  }
373  return true;
374 }
375 
376 
377 void
378 Reader::addComment( Location begin,
379  Location end,
380  CommentPlacement placement )
381 {
382  assert( collectComments_ );
383  if ( placement == commentAfterOnSameLine )
384  {
385  assert( lastValue_ != 0 );
386  lastValue_->setComment( std::string( begin, end ), placement );
387  }
388  else
389  {
390  if ( !commentsBefore_.empty() )
391  commentsBefore_ += "\n";
392  commentsBefore_ += std::string( begin, end );
393  }
394 }
395 
396 
397 bool
398 Reader::readCStyleComment()
399 {
400  while ( current_ != end_ )
401  {
402  Char c = getNextChar();
403  if ( c == '*' && *current_ == '/' )
404  break;
405  }
406  return getNextChar() == '/';
407 }
408 
409 
410 bool
411 Reader::readCppStyleComment()
412 {
413  while ( current_ != end_ )
414  {
415  Char c = getNextChar();
416  if ( c == '\r' || c == '\n' )
417  break;
418  }
419  return true;
420 }
421 
422 
423 void
424 Reader::readNumber()
425 {
426  while ( current_ != end_ )
427  {
428  if ( !(*current_ >= '0' && *current_ <= '9') &&
429  !in( *current_, '.', 'e', 'E', '+', '-' ) )
430  break;
431  ++current_;
432  }
433 }
434 
435 bool
436 Reader::readString()
437 {
438  Char c = 0;
439  while ( current_ != end_ )
440  {
441  c = getNextChar();
442  if ( c == '\\' )
443  getNextChar();
444  else if ( c == '"' )
445  break;
446  }
447  return c == '"';
448 }
449 
450 
451 bool
452 Reader::readObject( Token &/*tokenStart*/ )
453 {
454  Token tokenName;
455  std::string name;
456  currentValue() = Value( objectValue );
457  while ( readToken( tokenName ) )
458  {
459  bool initialTokenOk = true;
460  while ( tokenName.type_ == tokenComment && initialTokenOk )
461  initialTokenOk = readToken( tokenName );
462  if ( !initialTokenOk )
463  break;
464  if ( tokenName.type_ == tokenObjectEnd && name.empty() ) // empty object
465  return true;
466  if ( tokenName.type_ != tokenString )
467  break;
468 
469  name = "";
470  if ( !decodeString( tokenName, name ) )
471  return recoverFromError( tokenObjectEnd );
472 
473  Token colon;
474  if ( !readToken( colon ) || colon.type_ != tokenMemberSeparator )
475  {
476  return addErrorAndRecover( "Missing ':' after object member name",
477  colon,
478  tokenObjectEnd );
479  }
480  Value &value = currentValue()[ name ];
481  nodes_.push( &value );
482  bool ok = readValue();
483  nodes_.pop();
484  if ( !ok ) // error already set
485  return recoverFromError( tokenObjectEnd );
486 
487  Token comma;
488  if ( !readToken( comma )
489  || ( comma.type_ != tokenObjectEnd &&
490  comma.type_ != tokenArraySeparator &&
491  comma.type_ != tokenComment ) )
492  {
493  return addErrorAndRecover( "Missing ',' or '}' in object declaration",
494  comma,
495  tokenObjectEnd );
496  }
497  bool finalizeTokenOk = true;
498  while ( comma.type_ == tokenComment &&
499  finalizeTokenOk )
500  finalizeTokenOk = readToken( comma );
501  if ( comma.type_ == tokenObjectEnd )
502  return true;
503  }
504  return addErrorAndRecover( "Missing '}' or object member name",
505  tokenName,
506  tokenObjectEnd );
507 }
508 
509 
510 bool
511 Reader::readArray( Token &/*tokenStart*/ )
512 {
513  currentValue() = Value( arrayValue );
514  skipSpaces();
515  if ( *current_ == ']' ) // empty array
516  {
517  Token endArray;
518  readToken( endArray );
519  return true;
520  }
521  int index = 0;
522  for (;;)
523  {
524  Value &value = currentValue()[ index++ ];
525  nodes_.push( &value );
526  bool ok = readValue();
527  nodes_.pop();
528  if ( !ok ) // error already set
529  return recoverFromError( tokenArrayEnd );
530 
531  Token token;
532  // Accept Comment after last item in the array.
533  ok = readToken( token );
534  while ( token.type_ == tokenComment && ok )
535  {
536  ok = readToken( token );
537  }
538  bool badTokenType = ( token.type_ != tokenArraySeparator &&
539  token.type_ != tokenArrayEnd );
540  if ( !ok || badTokenType )
541  {
542  return addErrorAndRecover( "Missing ',' or ']' in array declaration",
543  token,
544  tokenArrayEnd );
545  }
546  if ( token.type_ == tokenArrayEnd )
547  break;
548  }
549  return true;
550 }
551 
552 
553 bool
554 Reader::decodeNumber( Token &token )
555 {
556  bool isDouble = false;
557  for ( Location inspect = token.start_; inspect != token.end_; ++inspect )
558  {
559  isDouble = isDouble
560  || in( *inspect, '.', 'e', 'E', '+' )
561  || ( *inspect == '-' && inspect != token.start_ );
562  }
563  if ( isDouble )
564  return decodeDouble( token );
565  // Attempts to parse the number as an integer. If the number is
566  // larger than the maximum supported value of an integer then
567  // we decode the number as a double.
568  Location current = token.start_;
569  bool isNegative = *current == '-';
570  if ( isNegative )
571  ++current;
572  Value::LargestUInt maxIntegerValue = isNegative ? Value::LargestUInt(-Value::minLargestInt)
573  : Value::maxLargestUInt;
574  Value::LargestUInt threshold = maxIntegerValue / 10;
575  Value::UInt lastDigitThreshold = Value::UInt( maxIntegerValue % 10 );
576  assert( lastDigitThreshold >=0 && lastDigitThreshold <= 9 );
577  Value::LargestUInt value = 0;
578  while ( current < token.end_ )
579  {
580  Char c = *current++;
581  if ( c < '0' || c > '9' )
582  return addError( "'" + std::string( token.start_, token.end_ ) + "' is not a number.", token );
583  Value::UInt digit(c - '0');
584  if ( value >= threshold )
585  {
586  // If the current digit is not the last one, or if it is
587  // greater than the last digit of the maximum integer value,
588  // the parse the number as a double.
589  if ( current != token.end_ || digit > lastDigitThreshold )
590  {
591  return decodeDouble( token );
592  }
593  }
594  value = value * 10 + digit;
595  }
596  if ( isNegative )
597  currentValue() = -Value::LargestInt( value );
598  else if ( value <= Value::LargestUInt(Value::maxInt) )
599  currentValue() = Value::LargestInt( value );
600  else
601  currentValue() = value;
602  return true;
603 }
604 
605 
606 bool
607 Reader::decodeDouble( Token &token )
608 {
609  double value = 0;
610  const int bufferSize = 32;
611  int count;
612  int length = int(token.end_ - token.start_);
613  if ( length <= bufferSize )
614  {
615  Char buffer[bufferSize+1];
616  memcpy( buffer, token.start_, length );
617  buffer[length] = 0;
618  count = sscanf( buffer, "%lf", &value );
619  }
620  else
621  {
622  std::string buffer( token.start_, token.end_ );
623  count = sscanf( buffer.c_str(), "%lf", &value );
624  }
625 
626  if ( count != 1 )
627  return addError( "'" + std::string( token.start_, token.end_ ) + "' is not a number.", token );
628  currentValue() = value;
629  return true;
630 }
631 
632 
633 bool
634 Reader::decodeString( Token &token )
635 {
636  std::string decoded;
637  if ( !decodeString( token, decoded ) )
638  return false;
639  currentValue() = decoded;
640  return true;
641 }
642 
643 
644 bool
645 Reader::decodeString( Token &token, std::string &decoded )
646 {
647  decoded.reserve( token.end_ - token.start_ - 2 );
648  Location current = token.start_ + 1; // skip '"'
649  Location end = token.end_ - 1; // do not include '"'
650  while ( current != end )
651  {
652  Char c = *current++;
653  if ( c == '"' )
654  break;
655  else if ( c == '\\' )
656  {
657  if ( current == end )
658  return addError( "Empty escape sequence in string", token, current );
659  Char escape = *current++;
660  switch ( escape )
661  {
662  case '"': decoded += '"'; break;
663  case '/': decoded += '/'; break;
664  case '\\': decoded += '\\'; break;
665  case 'b': decoded += '\b'; break;
666  case 'f': decoded += '\f'; break;
667  case 'n': decoded += '\n'; break;
668  case 'r': decoded += '\r'; break;
669  case 't': decoded += '\t'; break;
670  case 'u':
671  {
672  unsigned int unicode;
673  if ( !decodeUnicodeCodePoint( token, current, end, unicode ) )
674  return false;
675  decoded += codePointToUTF8(unicode);
676  }
677  break;
678  default:
679  return addError( "Bad escape sequence in string", token, current );
680  }
681  }
682  else
683  {
684  decoded += c;
685  }
686  }
687  return true;
688 }
689 
690 bool
691 Reader::decodeUnicodeCodePoint( Token &token,
692  Location &current,
693  Location end,
694  unsigned int &unicode )
695 {
696 
697  if ( !decodeUnicodeEscapeSequence( token, current, end, unicode ) )
698  return false;
699  if (unicode >= 0xD800 && unicode <= 0xDBFF)
700  {
701  // surrogate pairs
702  if (end - current < 6)
703  return addError( "additional six characters expected to parse unicode surrogate pair.", token, current );
704  unsigned int surrogatePair;
705  if (*(current++) == '\\' && *(current++)== 'u')
706  {
707  if (decodeUnicodeEscapeSequence( token, current, end, surrogatePair ))
708  {
709  unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF);
710  }
711  else
712  return false;
713  }
714  else
715  return addError( "expecting another \\u token to begin the second half of a unicode surrogate pair", token, current );
716  }
717  return true;
718 }
719 
720 bool
721 Reader::decodeUnicodeEscapeSequence( Token &token,
722  Location &current,
723  Location end,
724  unsigned int &unicode )
725 {
726  if ( end - current < 4 )
727  return addError( "Bad unicode escape sequence in string: four digits expected.", token, current );
728  unicode = 0;
729  for ( int index =0; index < 4; ++index )
730  {
731  Char c = *current++;
732  unicode *= 16;
733  if ( c >= '0' && c <= '9' )
734  unicode += c - '0';
735  else if ( c >= 'a' && c <= 'f' )
736  unicode += c - 'a' + 10;
737  else if ( c >= 'A' && c <= 'F' )
738  unicode += c - 'A' + 10;
739  else
740  return addError( "Bad unicode escape sequence in string: hexadecimal digit expected.", token, current );
741  }
742  return true;
743 }
744 
745 
746 bool
747 Reader::addError( const std::string &message,
748  Token &token,
749  Location extra )
750 {
751  ErrorInfo info;
752  info.token_ = token;
753  info.message_ = message;
754  info.extra_ = extra;
755  errors_.push_back( info );
756  return false;
757 }
758 
759 
760 bool
761 Reader::recoverFromError( TokenType skipUntilToken )
762 {
763  int errorCount = int(errors_.size());
764  Token skip;
765  for (;;)
766  {
767  if ( !readToken(skip) )
768  errors_.resize( errorCount ); // discard errors caused by recovery
769  if ( skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream )
770  break;
771  }
772  errors_.resize( errorCount );
773  return false;
774 }
775 
776 
777 bool
778 Reader::addErrorAndRecover( const std::string &message,
779  Token &token,
780  TokenType skipUntilToken )
781 {
782  addError( message, token );
783  return recoverFromError( skipUntilToken );
784 }
785 
786 
787 Value &
788 Reader::currentValue()
789 {
790  return *(nodes_.top());
791 }
792 
793 
795 Reader::getNextChar()
796 {
797  if ( current_ == end_ )
798  return 0;
799  return *current_++;
800 }
801 
802 
803 void
804 Reader::getLocationLineAndColumn( Location location,
805  int &line,
806  int &column ) const
807 {
808  Location current = begin_;
809  Location lastLineStart = current;
810  line = 0;
811  while ( current < location && current != end_ )
812  {
813  Char c = *current++;
814  if ( c == '\r' )
815  {
816  if ( *current == '\n' )
817  ++current;
818  lastLineStart = current;
819  ++line;
820  }
821  else if ( c == '\n' )
822  {
823  lastLineStart = current;
824  ++line;
825  }
826  }
827  // column & line start at 1
828  column = int(location - lastLineStart) + 1;
829  ++line;
830 }
831 
832 
833 std::string
834 Reader::getLocationLineAndColumn( Location location ) const
835 {
836  int line, column;
837  getLocationLineAndColumn( location, line, column );
838  char buffer[18+16+16+1];
839  sprintf( buffer, "Line %d, Column %d", line, column );
840  return buffer;
841 }
842 
843 
844 // Deprecated. Preserved for backward compatibility
845 std::string
847 {
848  return getFormattedErrorMessages();
849 }
850 
851 
852 std::string
854 {
855  std::string formattedMessage;
856  for ( Errors::const_iterator itError = errors_.begin();
857  itError != errors_.end();
858  ++itError )
859  {
860  const ErrorInfo &error = *itError;
861  formattedMessage += "* " + getLocationLineAndColumn( error.token_.start_ ) + "\n";
862  formattedMessage += " " + error.message_ + "\n";
863  if ( error.extra_ )
864  formattedMessage += "See " + getLocationLineAndColumn( error.extra_ ) + " for detail.\n";
865  }
866  return formattedMessage;
867 }
868 
869 
870 std::istream& operator>>( std::istream &sin, Value &root )
871 {
872  Json::Reader reader;
873  bool ok = reader.parse(sin, root, true);
874  //JSON_ASSERT( ok );
875  if (!ok) throw std::runtime_error(reader.getFormattedErrorMessages());
876  return sin;
877 }
878 
879 
880 } // namespace Json
static std::string codePointToUTF8(unsigned int cp)
Converts a unicode code-point to UTF-8.
Definition: json_tool.h:19
array value (ordered list)
Definition: value.h:38
object value (collection of name/value pairs).
Definition: value.h:39
std::istream & operator>>(std::istream &, Value &)
Read from 'sin' into 'root'.
char Char
Definition: reader.h:26
std::string getFormatedErrorMessages() const
Returns a user friendly string that list errors in the parsed document.
static const Int maxInt
Maximum signed int value that can be stored in a Json::Value.
Definition: value.h:150
Json::LargestUInt LargestUInt
Definition: value.h:136
Features()
Initialize the configuration like JsonConfig::allFeatures;.
Definition: json_reader.cpp:27
bool isObject() const
void setComment(const char *comment, CommentPlacement placement)
Comments must be //... or /* ... */.
static const LargestInt minLargestInt
Minimum signed integer value that can be stored in a Json::Value.
Definition: value.h:141
bool allowComments_
true if comments are allowed. Default: true.
Definition: features.h:41
CommentPlacement
Definition: value.h:42
const Char * Location
Definition: reader.h:27
bool parse(const std::string &document, Value &root, bool collectComments=true)
Read a Value from a JSON document.
Definition: json_reader.cpp:94
static bool in(Reader::Char c, Reader::Char c1, Reader::Char c2, Reader::Char c3, Reader::Char c4)
Definition: json_reader.cpp:55
JSON (JavaScript Object Notation).
Definition: config.h:73
Json::LargestInt LargestInt
Definition: value.h:135
Json::UInt UInt
Definition: value.h:129
Represents a JSON value.
Definition: value.h:118
static Features all()
A configuration that allows all features and assumes all strings are UTF-8.
Definition: json_reader.cpp:35
a comment on the line after a value (only make sense for root value)
Definition: value.h:46
Unserialize a JSON document into a Value.
Definition: reader.h:23
static Features strictMode()
A configuration that is strictly compatible with the JSON specification.
Definition: json_reader.cpp:42
bool strictRoot_
true if root must be either an array or an object value. Default: false.
Definition: features.h:44
bool isArray() const
static bool containsNewLine(Reader::Location begin, Reader::Location end)
Definition: json_reader.cpp:68
Configuration passed to reader and writer.
Definition: features.h:19
a comment placed on the line before a value
Definition: value.h:44
Reader()
Constructs a Reader allowing all features for parsing.
Definition: json_reader.cpp:81
std::string getFormattedErrorMessages() const
Returns a user friendly string that list errors in the parsed document.
a comment just after a value on the same line
Definition: value.h:45

SourceForge Logo hosts this site. Send comments to:
Json-cpp Developers