You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
838 lines
20 KiB
838 lines
20 KiB
// Copyright 2007-2010 Baptiste Lepilleur |
|
// Distributed under MIT license, or public domain if desired and |
|
// recognized in your jurisdiction. |
|
// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE |
|
|
|
#if !defined(JSON_IS_AMALGAMATION) |
|
# include <json/writer.h> |
|
# include "json_tool.h" |
|
#endif // if !defined(JSON_IS_AMALGAMATION) |
|
#include <utility> |
|
#include <assert.h> |
|
#include <stdio.h> |
|
#include <string.h> |
|
#include <iostream> |
|
#include <sstream> |
|
#include <iomanip> |
|
|
|
#if _MSC_VER >= 1400 // VC++ 8.0 |
|
#pragma warning( disable : 4996 ) // disable warning about strdup being deprecated. |
|
#endif |
|
|
|
namespace Json { |
|
|
|
static bool containsControlCharacter( const char* str ) |
|
{ |
|
while ( *str ) |
|
{ |
|
if ( isControlCharacter( *(str++) ) ) |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
|
|
std::string valueToString( LargestInt value ) |
|
{ |
|
UIntToStringBuffer buffer; |
|
char *current = buffer + sizeof(buffer); |
|
bool isNegative = value < 0; |
|
if ( isNegative ) |
|
value = -value; |
|
uintToString( LargestUInt(value), current ); |
|
if ( isNegative ) |
|
*--current = '-'; |
|
assert( current >= buffer ); |
|
return current; |
|
} |
|
|
|
|
|
std::string valueToString( LargestUInt value ) |
|
{ |
|
UIntToStringBuffer buffer; |
|
char *current = buffer + sizeof(buffer); |
|
uintToString( value, current ); |
|
assert( current >= buffer ); |
|
return current; |
|
} |
|
|
|
#if defined(JSON_HAS_INT64) |
|
|
|
std::string valueToString( Int value ) |
|
{ |
|
return valueToString( LargestInt(value) ); |
|
} |
|
|
|
|
|
std::string valueToString( UInt value ) |
|
{ |
|
return valueToString( LargestUInt(value) ); |
|
} |
|
|
|
#endif // # if defined(JSON_HAS_INT64) |
|
|
|
|
|
std::string valueToString( double value ) |
|
{ |
|
char buffer[32]; |
|
#if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__) // Use secure version with visual studio 2005 to avoid warning. |
|
sprintf_s(buffer, sizeof(buffer), "%#.16g", value); |
|
#else |
|
sprintf(buffer, "%#.16g", value); |
|
#endif |
|
char* ch = buffer + strlen(buffer) - 1; |
|
if (*ch != '0') return buffer; // nothing to truncate, so save time |
|
while(ch > buffer && *ch == '0'){ |
|
--ch; |
|
} |
|
char* last_nonzero = ch; |
|
while(ch >= buffer){ |
|
switch(*ch){ |
|
case '0': |
|
case '1': |
|
case '2': |
|
case '3': |
|
case '4': |
|
case '5': |
|
case '6': |
|
case '7': |
|
case '8': |
|
case '9': |
|
--ch; |
|
continue; |
|
case '.': |
|
// Truncate zeroes to save bytes in output, but keep one. |
|
*(last_nonzero+2) = '\0'; |
|
return buffer; |
|
default: |
|
return buffer; |
|
} |
|
} |
|
return buffer; |
|
} |
|
|
|
|
|
std::string valueToString( bool value ) |
|
{ |
|
return value ? "true" : "false"; |
|
} |
|
|
|
std::string valueToQuotedString( const char *value ) |
|
{ |
|
// Not sure how to handle unicode... |
|
if (strpbrk(value, "\"\\\b\f\n\r\t") == NULL && !containsControlCharacter( value )) |
|
return std::string("\"") + value + "\""; |
|
// We have to walk value and escape any special characters. |
|
// Appending to std::string is not efficient, but this should be rare. |
|
// (Note: forward slashes are *not* rare, but I am not escaping them.) |
|
std::string::size_type maxsize = strlen(value)*2 + 3; // allescaped+quotes+NULL |
|
std::string result; |
|
result.reserve(maxsize); // to avoid lots of mallocs |
|
result += "\""; |
|
for (const char* c=value; *c != 0; ++c) |
|
{ |
|
switch(*c) |
|
{ |
|
case '\"': |
|
result += "\\\""; |
|
break; |
|
case '\\': |
|
result += "\\\\"; |
|
break; |
|
case '\b': |
|
result += "\\b"; |
|
break; |
|
case '\f': |
|
result += "\\f"; |
|
break; |
|
case '\n': |
|
result += "\\n"; |
|
break; |
|
case '\r': |
|
result += "\\r"; |
|
break; |
|
case '\t': |
|
result += "\\t"; |
|
break; |
|
//case '/': |
|
// Even though \/ is considered a legal escape in JSON, a bare |
|
// slash is also legal, so I see no reason to escape it. |
|
// (I hope I am not misunderstanding something. |
|
// blep notes: actually escaping \/ may be useful in javascript to avoid </ |
|
// sequence. |
|
// Should add a flag to allow this compatibility mode and prevent this |
|
// sequence from occurring. |
|
default: |
|
if ( isControlCharacter( *c ) ) |
|
{ |
|
std::ostringstream oss; |
|
oss << "\\u" << std::hex << std::uppercase << std::setfill('0') << std::setw(4) << static_cast<int>(*c); |
|
result += oss.str(); |
|
} |
|
else |
|
{ |
|
result += *c; |
|
} |
|
break; |
|
} |
|
} |
|
result += "\""; |
|
return result; |
|
} |
|
|
|
// Class Writer |
|
// ////////////////////////////////////////////////////////////////// |
|
Writer::~Writer() |
|
{ |
|
} |
|
|
|
|
|
// Class FastWriter |
|
// ////////////////////////////////////////////////////////////////// |
|
|
|
FastWriter::FastWriter() |
|
: yamlCompatiblityEnabled_( false ) |
|
{ |
|
} |
|
|
|
|
|
void |
|
FastWriter::enableYAMLCompatibility() |
|
{ |
|
yamlCompatiblityEnabled_ = true; |
|
} |
|
|
|
|
|
std::string |
|
FastWriter::write( const Value &root ) |
|
{ |
|
document_ = ""; |
|
writeValue( root ); |
|
document_ += "\n"; |
|
return document_; |
|
} |
|
|
|
|
|
void |
|
FastWriter::writeValue( const Value &value ) |
|
{ |
|
switch ( value.type() ) |
|
{ |
|
case nullValue: |
|
document_ += "null"; |
|
break; |
|
case intValue: |
|
document_ += valueToString( value.asLargestInt() ); |
|
break; |
|
case uintValue: |
|
document_ += valueToString( value.asLargestUInt() ); |
|
break; |
|
case realValue: |
|
document_ += valueToString( value.asDouble() ); |
|
break; |
|
case stringValue: |
|
document_ += valueToQuotedString( value.asCString() ); |
|
break; |
|
case booleanValue: |
|
document_ += valueToString( value.asBool() ); |
|
break; |
|
case arrayValue: |
|
{ |
|
document_ += "["; |
|
int size = value.size(); |
|
for ( int index =0; index < size; ++index ) |
|
{ |
|
if ( index > 0 ) |
|
document_ += ","; |
|
writeValue( value[index] ); |
|
} |
|
document_ += "]"; |
|
} |
|
break; |
|
case objectValue: |
|
{ |
|
Value::Members members( value.getMemberNames() ); |
|
document_ += "{"; |
|
for ( Value::Members::iterator it = members.begin(); |
|
it != members.end(); |
|
++it ) |
|
{ |
|
const std::string &name = *it; |
|
if ( it != members.begin() ) |
|
document_ += ","; |
|
document_ += valueToQuotedString( name.c_str() ); |
|
document_ += yamlCompatiblityEnabled_ ? ": " |
|
: ":"; |
|
writeValue( value[name] ); |
|
} |
|
document_ += "}"; |
|
} |
|
break; |
|
} |
|
} |
|
|
|
|
|
// Class StyledWriter |
|
// ////////////////////////////////////////////////////////////////// |
|
|
|
StyledWriter::StyledWriter() |
|
: rightMargin_( 74 ) |
|
, indentSize_( 3 ) |
|
{ |
|
} |
|
|
|
|
|
std::string |
|
StyledWriter::write( const Value &root ) |
|
{ |
|
document_ = ""; |
|
addChildValues_ = false; |
|
indentString_ = ""; |
|
writeCommentBeforeValue( root ); |
|
writeValue( root ); |
|
writeCommentAfterValueOnSameLine( root ); |
|
document_ += "\n"; |
|
return document_; |
|
} |
|
|
|
|
|
void |
|
StyledWriter::writeValue( const Value &value ) |
|
{ |
|
switch ( value.type() ) |
|
{ |
|
case nullValue: |
|
pushValue( "null" ); |
|
break; |
|
case intValue: |
|
pushValue( valueToString( value.asLargestInt() ) ); |
|
break; |
|
case uintValue: |
|
pushValue( valueToString( value.asLargestUInt() ) ); |
|
break; |
|
case realValue: |
|
pushValue( valueToString( value.asDouble() ) ); |
|
break; |
|
case stringValue: |
|
pushValue( valueToQuotedString( value.asCString() ) ); |
|
break; |
|
case booleanValue: |
|
pushValue( valueToString( value.asBool() ) ); |
|
break; |
|
case arrayValue: |
|
writeArrayValue( value); |
|
break; |
|
case objectValue: |
|
{ |
|
Value::Members members( value.getMemberNames() ); |
|
if ( members.empty() ) |
|
pushValue( "{}" ); |
|
else |
|
{ |
|
writeWithIndent( "{" ); |
|
indent(); |
|
Value::Members::iterator it = members.begin(); |
|
for (;;) |
|
{ |
|
const std::string &name = *it; |
|
const Value &childValue = value[name]; |
|
writeCommentBeforeValue( childValue ); |
|
writeWithIndent( valueToQuotedString( name.c_str() ) ); |
|
document_ += " : "; |
|
writeValue( childValue ); |
|
if ( ++it == members.end() ) |
|
{ |
|
writeCommentAfterValueOnSameLine( childValue ); |
|
break; |
|
} |
|
document_ += ","; |
|
writeCommentAfterValueOnSameLine( childValue ); |
|
} |
|
unindent(); |
|
writeWithIndent( "}" ); |
|
} |
|
} |
|
break; |
|
} |
|
} |
|
|
|
|
|
void |
|
StyledWriter::writeArrayValue( const Value &value ) |
|
{ |
|
unsigned size = value.size(); |
|
if ( size == 0 ) |
|
pushValue( "[]" ); |
|
else |
|
{ |
|
bool isArrayMultiLine = isMultineArray( value ); |
|
if ( isArrayMultiLine ) |
|
{ |
|
writeWithIndent( "[" ); |
|
indent(); |
|
bool hasChildValue = !childValues_.empty(); |
|
unsigned index =0; |
|
for (;;) |
|
{ |
|
const Value &childValue = value[index]; |
|
writeCommentBeforeValue( childValue ); |
|
if ( hasChildValue ) |
|
writeWithIndent( childValues_[index] ); |
|
else |
|
{ |
|
writeIndent(); |
|
writeValue( childValue ); |
|
} |
|
if ( ++index == size ) |
|
{ |
|
writeCommentAfterValueOnSameLine( childValue ); |
|
break; |
|
} |
|
document_ += ","; |
|
writeCommentAfterValueOnSameLine( childValue ); |
|
} |
|
unindent(); |
|
writeWithIndent( "]" ); |
|
} |
|
else // output on a single line |
|
{ |
|
assert( childValues_.size() == size ); |
|
document_ += "[ "; |
|
for ( unsigned index =0; index < size; ++index ) |
|
{ |
|
if ( index > 0 ) |
|
document_ += ", "; |
|
document_ += childValues_[index]; |
|
} |
|
document_ += " ]"; |
|
} |
|
} |
|
} |
|
|
|
|
|
bool |
|
StyledWriter::isMultineArray( const Value &value ) |
|
{ |
|
int size = value.size(); |
|
bool isMultiLine = size*3 >= rightMargin_ ; |
|
childValues_.clear(); |
|
for ( int index =0; index < size && !isMultiLine; ++index ) |
|
{ |
|
const Value &childValue = value[index]; |
|
isMultiLine = isMultiLine || |
|
( (childValue.isArray() || childValue.isObject()) && |
|
childValue.size() > 0 ); |
|
} |
|
if ( !isMultiLine ) // check if line length > max line length |
|
{ |
|
childValues_.reserve( size ); |
|
addChildValues_ = true; |
|
int lineLength = 4 + (size-1)*2; // '[ ' + ', '*n + ' ]' |
|
for ( int index =0; index < size && !isMultiLine; ++index ) |
|
{ |
|
writeValue( value[index] ); |
|
lineLength += int( childValues_[index].length() ); |
|
isMultiLine = isMultiLine && hasCommentForValue( value[index] ); |
|
} |
|
addChildValues_ = false; |
|
isMultiLine = isMultiLine || lineLength >= rightMargin_; |
|
} |
|
return isMultiLine; |
|
} |
|
|
|
|
|
void |
|
StyledWriter::pushValue( const std::string &value ) |
|
{ |
|
if ( addChildValues_ ) |
|
childValues_.push_back( value ); |
|
else |
|
document_ += value; |
|
} |
|
|
|
|
|
void |
|
StyledWriter::writeIndent() |
|
{ |
|
if ( !document_.empty() ) |
|
{ |
|
char last = document_[document_.length()-1]; |
|
if ( last == ' ' ) // already indented |
|
return; |
|
if ( last != '\n' ) // Comments may add new-line |
|
document_ += '\n'; |
|
} |
|
document_ += indentString_; |
|
} |
|
|
|
|
|
void |
|
StyledWriter::writeWithIndent( const std::string &value ) |
|
{ |
|
writeIndent(); |
|
document_ += value; |
|
} |
|
|
|
|
|
void |
|
StyledWriter::indent() |
|
{ |
|
indentString_ += std::string( indentSize_, ' ' ); |
|
} |
|
|
|
|
|
void |
|
StyledWriter::unindent() |
|
{ |
|
assert( int(indentString_.size()) >= indentSize_ ); |
|
indentString_.resize( indentString_.size() - indentSize_ ); |
|
} |
|
|
|
|
|
void |
|
StyledWriter::writeCommentBeforeValue( const Value &root ) |
|
{ |
|
if ( !root.hasComment( commentBefore ) ) |
|
return; |
|
document_ += normalizeEOL( root.getComment( commentBefore ) ); |
|
document_ += "\n"; |
|
} |
|
|
|
|
|
void |
|
StyledWriter::writeCommentAfterValueOnSameLine( const Value &root ) |
|
{ |
|
if ( root.hasComment( commentAfterOnSameLine ) ) |
|
document_ += " " + normalizeEOL( root.getComment( commentAfterOnSameLine ) ); |
|
|
|
if ( root.hasComment( commentAfter ) ) |
|
{ |
|
document_ += "\n"; |
|
document_ += normalizeEOL( root.getComment( commentAfter ) ); |
|
document_ += "\n"; |
|
} |
|
} |
|
|
|
|
|
bool |
|
StyledWriter::hasCommentForValue( const Value &value ) |
|
{ |
|
return value.hasComment( commentBefore ) |
|
|| value.hasComment( commentAfterOnSameLine ) |
|
|| value.hasComment( commentAfter ); |
|
} |
|
|
|
|
|
std::string |
|
StyledWriter::normalizeEOL( const std::string &text ) |
|
{ |
|
std::string normalized; |
|
normalized.reserve( text.length() ); |
|
const char *begin = text.c_str(); |
|
const char *end = begin + text.length(); |
|
const char *current = begin; |
|
while ( current != end ) |
|
{ |
|
char c = *current++; |
|
if ( c == '\r' ) // mac or dos EOL |
|
{ |
|
if ( *current == '\n' ) // convert dos EOL |
|
++current; |
|
normalized += '\n'; |
|
} |
|
else // handle unix EOL & other char |
|
normalized += c; |
|
} |
|
return normalized; |
|
} |
|
|
|
|
|
// Class StyledStreamWriter |
|
// ////////////////////////////////////////////////////////////////// |
|
|
|
StyledStreamWriter::StyledStreamWriter( std::string indentation ) |
|
: document_(NULL) |
|
, rightMargin_( 74 ) |
|
, indentation_( indentation ) |
|
{ |
|
} |
|
|
|
|
|
void |
|
StyledStreamWriter::write( std::ostream &out, const Value &root ) |
|
{ |
|
document_ = &out; |
|
addChildValues_ = false; |
|
indentString_ = ""; |
|
writeCommentBeforeValue( root ); |
|
writeValue( root ); |
|
writeCommentAfterValueOnSameLine( root ); |
|
*document_ << "\n"; |
|
document_ = NULL; // Forget the stream, for safety. |
|
} |
|
|
|
|
|
void |
|
StyledStreamWriter::writeValue( const Value &value ) |
|
{ |
|
switch ( value.type() ) |
|
{ |
|
case nullValue: |
|
pushValue( "null" ); |
|
break; |
|
case intValue: |
|
pushValue( valueToString( value.asLargestInt() ) ); |
|
break; |
|
case uintValue: |
|
pushValue( valueToString( value.asLargestUInt() ) ); |
|
break; |
|
case realValue: |
|
pushValue( valueToString( value.asDouble() ) ); |
|
break; |
|
case stringValue: |
|
pushValue( valueToQuotedString( value.asCString() ) ); |
|
break; |
|
case booleanValue: |
|
pushValue( valueToString( value.asBool() ) ); |
|
break; |
|
case arrayValue: |
|
writeArrayValue( value); |
|
break; |
|
case objectValue: |
|
{ |
|
Value::Members members( value.getMemberNames() ); |
|
if ( members.empty() ) |
|
pushValue( "{}" ); |
|
else |
|
{ |
|
writeWithIndent( "{" ); |
|
indent(); |
|
Value::Members::iterator it = members.begin(); |
|
for (;;) |
|
{ |
|
const std::string &name = *it; |
|
const Value &childValue = value[name]; |
|
writeCommentBeforeValue( childValue ); |
|
writeWithIndent( valueToQuotedString( name.c_str() ) ); |
|
*document_ << " : "; |
|
writeValue( childValue ); |
|
if ( ++it == members.end() ) |
|
{ |
|
writeCommentAfterValueOnSameLine( childValue ); |
|
break; |
|
} |
|
*document_ << ","; |
|
writeCommentAfterValueOnSameLine( childValue ); |
|
} |
|
unindent(); |
|
writeWithIndent( "}" ); |
|
} |
|
} |
|
break; |
|
} |
|
} |
|
|
|
|
|
void |
|
StyledStreamWriter::writeArrayValue( const Value &value ) |
|
{ |
|
unsigned size = value.size(); |
|
if ( size == 0 ) |
|
pushValue( "[]" ); |
|
else |
|
{ |
|
bool isArrayMultiLine = isMultineArray( value ); |
|
if ( isArrayMultiLine ) |
|
{ |
|
writeWithIndent( "[" ); |
|
indent(); |
|
bool hasChildValue = !childValues_.empty(); |
|
unsigned index =0; |
|
for (;;) |
|
{ |
|
const Value &childValue = value[index]; |
|
writeCommentBeforeValue( childValue ); |
|
if ( hasChildValue ) |
|
writeWithIndent( childValues_[index] ); |
|
else |
|
{ |
|
writeIndent(); |
|
writeValue( childValue ); |
|
} |
|
if ( ++index == size ) |
|
{ |
|
writeCommentAfterValueOnSameLine( childValue ); |
|
break; |
|
} |
|
*document_ << ","; |
|
writeCommentAfterValueOnSameLine( childValue ); |
|
} |
|
unindent(); |
|
writeWithIndent( "]" ); |
|
} |
|
else // output on a single line |
|
{ |
|
assert( childValues_.size() == size ); |
|
*document_ << "[ "; |
|
for ( unsigned index =0; index < size; ++index ) |
|
{ |
|
if ( index > 0 ) |
|
*document_ << ", "; |
|
*document_ << childValues_[index]; |
|
} |
|
*document_ << " ]"; |
|
} |
|
} |
|
} |
|
|
|
|
|
bool |
|
StyledStreamWriter::isMultineArray( const Value &value ) |
|
{ |
|
int size = value.size(); |
|
bool isMultiLine = size*3 >= rightMargin_ ; |
|
childValues_.clear(); |
|
for ( int index =0; index < size && !isMultiLine; ++index ) |
|
{ |
|
const Value &childValue = value[index]; |
|
isMultiLine = isMultiLine || |
|
( (childValue.isArray() || childValue.isObject()) && |
|
childValue.size() > 0 ); |
|
} |
|
if ( !isMultiLine ) // check if line length > max line length |
|
{ |
|
childValues_.reserve( size ); |
|
addChildValues_ = true; |
|
int lineLength = 4 + (size-1)*2; // '[ ' + ', '*n + ' ]' |
|
for ( int index =0; index < size && !isMultiLine; ++index ) |
|
{ |
|
writeValue( value[index] ); |
|
lineLength += int( childValues_[index].length() ); |
|
isMultiLine = isMultiLine && hasCommentForValue( value[index] ); |
|
} |
|
addChildValues_ = false; |
|
isMultiLine = isMultiLine || lineLength >= rightMargin_; |
|
} |
|
return isMultiLine; |
|
} |
|
|
|
|
|
void |
|
StyledStreamWriter::pushValue( const std::string &value ) |
|
{ |
|
if ( addChildValues_ ) |
|
childValues_.push_back( value ); |
|
else |
|
*document_ << value; |
|
} |
|
|
|
|
|
void |
|
StyledStreamWriter::writeIndent() |
|
{ |
|
/* |
|
Some comments in this method would have been nice. ;-) |
|
|
|
if ( !document_.empty() ) |
|
{ |
|
char last = document_[document_.length()-1]; |
|
if ( last == ' ' ) // already indented |
|
return; |
|
if ( last != '\n' ) // Comments may add new-line |
|
*document_ << '\n'; |
|
} |
|
*/ |
|
*document_ << '\n' << indentString_; |
|
} |
|
|
|
|
|
void |
|
StyledStreamWriter::writeWithIndent( const std::string &value ) |
|
{ |
|
writeIndent(); |
|
*document_ << value; |
|
} |
|
|
|
|
|
void |
|
StyledStreamWriter::indent() |
|
{ |
|
indentString_ += indentation_; |
|
} |
|
|
|
|
|
void |
|
StyledStreamWriter::unindent() |
|
{ |
|
assert( indentString_.size() >= indentation_.size() ); |
|
indentString_.resize( indentString_.size() - indentation_.size() ); |
|
} |
|
|
|
|
|
void |
|
StyledStreamWriter::writeCommentBeforeValue( const Value &root ) |
|
{ |
|
if ( !root.hasComment( commentBefore ) ) |
|
return; |
|
*document_ << normalizeEOL( root.getComment( commentBefore ) ); |
|
*document_ << "\n"; |
|
} |
|
|
|
|
|
void |
|
StyledStreamWriter::writeCommentAfterValueOnSameLine( const Value &root ) |
|
{ |
|
if ( root.hasComment( commentAfterOnSameLine ) ) |
|
*document_ << " " + normalizeEOL( root.getComment( commentAfterOnSameLine ) ); |
|
|
|
if ( root.hasComment( commentAfter ) ) |
|
{ |
|
*document_ << "\n"; |
|
*document_ << normalizeEOL( root.getComment( commentAfter ) ); |
|
*document_ << "\n"; |
|
} |
|
} |
|
|
|
|
|
bool |
|
StyledStreamWriter::hasCommentForValue( const Value &value ) |
|
{ |
|
return value.hasComment( commentBefore ) |
|
|| value.hasComment( commentAfterOnSameLine ) |
|
|| value.hasComment( commentAfter ); |
|
} |
|
|
|
|
|
std::string |
|
StyledStreamWriter::normalizeEOL( const std::string &text ) |
|
{ |
|
std::string normalized; |
|
normalized.reserve( text.length() ); |
|
const char *begin = text.c_str(); |
|
const char *end = begin + text.length(); |
|
const char *current = begin; |
|
while ( current != end ) |
|
{ |
|
char c = *current++; |
|
if ( c == '\r' ) // mac or dos EOL |
|
{ |
|
if ( *current == '\n' ) // convert dos EOL |
|
++current; |
|
normalized += '\n'; |
|
} |
|
else // handle unix EOL & other char |
|
normalized += c; |
|
} |
|
return normalized; |
|
} |
|
|
|
|
|
std::ostream& operator<<( std::ostream &sout, const Value &root ) |
|
{ |
|
Json::StyledStreamWriter writer; |
|
writer.write(sout, root); |
|
return sout; |
|
} |
|
|
|
|
|
} // namespace Json
|
|
|