diff --git a/doc/jsoncpp.dox b/doc/jsoncpp.dox index acde674..bf29778 100644 --- a/doc/jsoncpp.dox +++ b/doc/jsoncpp.dox @@ -53,23 +53,26 @@ preserved. Json::Value root; // 'root' will contain the root value after parsing. std::cin >> root; +// You can also read into a particular sub-value. +std::cin >> root["subtree"]; + // Get the value of the member of root named 'encoding', return 'UTF-8' if there is no // such member. std::string encoding = root.get("encoding", "UTF-8" ).asString(); -// Get the value of the member of root named 'encoding', return a 'null' value if +// Get the value of the member of root named 'encoding'; return a 'null' value if // there is no such member. const Json::Value plugins = root["plug-ins"]; for ( int index = 0; index < plugins.size(); ++index ) // Iterates over the sequence elements. loadPlugIn( plugins[index].asString() ); -setIndentLength( root["indent"].get("length", 3).asInt() ); -setIndentUseSpace( root["indent"].get("use_space", true).asBool() ); +foo::setIndentLength( root["indent"].get("length", 3).asInt() ); +foo::setIndentUseSpace( root["indent"].get("use_space", true).asBool() ); // Since Json::Value has implicit constructor for all value types, it is not // necessary to explicitly construct the Json::Value object: -root["encoding"] = getCurrentEncoding(); -root["indent"]["length"] = getCurrentIndentLength(); -root["indent"]["use_space"] = getCurrentIndentUseSpace(); +root["encoding"] = foo::getCurrentEncoding(); +root["indent"]["length"] = foo::getCurrentIndentLength(); +root["indent"]["use_space"] = foo::getCurrentIndentUseSpace(); // If you like the defaults, you can insert directly into a stream. std::cout << root; @@ -80,27 +83,40 @@ std::cout << std::endl; \endcode \section _advanced Advanced usage -We are finalizing the new *Builder* API, which will be in versions -`1.4.0` and `0.8.0` when released. Until then, you may continue to -use the old API, include `Writer`, `Reader`, and `Feature`. + +Configure *builders* to create *readers* and *writers*. For +configuration, we use our own `Json::Value` (rather than +standard setters/getters) so that we can add +features without losing binary-compatibility. + \code - -// EXPERIMENTAL -// Or use `writeString()` for convenience, with a specialized builder. +// For convenience, use `writeString()` with a specialized builder. Json::StreamWriterBuilder wbuilder; -builder.indentation_ = "\t"; -std::string document = Json::writeString(root, wbuilder); +wbuilder.settings_["indentation"] = "\t"; // simple Json::Value +std::string document = Json::writeString(wbuilder, root); -// You can also read into a particular sub-value. -std::cin >> root["subtree"]; - -// EXPERIMENTAL -// Here we use a specialized Builder, discard comments, and -// record errors. +// Here, using a specialized Builder, we discard comments and +// record errors as we parse. Json::CharReaderBuilder rbuilder; -rbuilder.collectComments_ = false; +rbuilder.settings_["collectComments"] = false; // simple Json::Value std::string errs; -Json::parseFromStream(rbuilder, std::cin, &root["subtree"], &errs); +bool ok = Json::parseFromStream(rbuilder, std::cin, &root, &errs); +\endcode + +Yes, compile-time configuration-checking would be helpful, +but `Json::Value` lets you +write and read the builder configuration, which is better! In other words, +you can configure your JSON parser using JSON. + +CharReaders and StreamWriters are not thread-safe, but they are re-usable. +\code +Json::CharReaderBuilder rbuilder; +cfg >> rbuilder.settings_; +std::unique_ptr const reader(rbuilder.newCharReader()); +reader->parse(start, stop, &value1, &errs); +// ... +reader->parse(start, stop, &value2, &errs); +// etc. \endcode \section _pbuild Build instructions @@ -137,5 +153,7 @@ and recognized in your jurisdiction. \author Baptiste Lepilleur (originator) \version \include version +We make strong guarantees about binary-compatibility, consistent with +the Apache versioning scheme. \sa version.h */ diff --git a/include/json/reader.h b/include/json/reader.h index d2bd140..c0dd137 100644 --- a/include/json/reader.h +++ b/include/json/reader.h @@ -270,7 +270,9 @@ public: class Factory { public: - /// \brief Allocate a CharReader via operator new(). + /** \brief Allocate a CharReader via operator new(). + * \throw std::exception if something goes wrong (e.g. invalid settings) + */ virtual CharReader* newCharReader() const = 0; }; // Factory }; // CharReader @@ -283,29 +285,53 @@ Usage: \code using namespace Json; CharReaderBuilder builder; - builder.collectComments_ = false; + builder.settings_["collectComments"] = false; Value value; std::string errs; bool ok = parseFromStream(builder, std::cin, &value, &errs); \endcode */ -class CharReaderBuilder : public CharReader::Factory { +class JSON_API CharReaderBuilder : public CharReader::Factory { public: - /** default: true - * - * It is possible to "allow" comments but still not "collect" them. - */ - bool collectComments_; - /** default: all() - * - * For historical reasons, Features is a separate structure. - */ - Features features_; + // Note: We use a Json::Value so that we can add data-members to this class + // without a major version bump. + /** Configuration of this builder. + These are case-sensitive. + Available settings (case-sensitive): + - "collectComments": false or true + - "allowComments" + - "strictRoot" + - "allowDroppedNullPlaceholders" + - "allowNumericKeys" + + You can examine 'settings_` yourself + to see the defaults. You can also write and read them just like any + JSON Value. + \sa setDefaults() + */ + Json::Value settings_; CharReaderBuilder(); virtual ~CharReaderBuilder(); virtual CharReader* newCharReader() const; + + /** \return true if 'settings' are legal and consistent; + * otherwise, indicate bad settings via 'invalid'. + */ + bool validate(Json::Value* invalid) const; + /** Called by ctor, but you can use this to reset settings_. + * \pre 'settings' != NULL (but Json::null is fine) + * \remark Defaults: + * \snippet src/lib_json/json_reader.cpp CharReaderBuilderStrictMode + */ + static void setDefaults(Json::Value* settings); + /** Same as old Features::strictMode(). + * \pre 'settings' != NULL (but Json::null is fine) + * \remark Defaults: + * \snippet src/lib_json/json_reader.cpp CharReaderBuilderDefaults + */ + static void strictMode(Json::Value* settings); }; /** Consume entire stream and use its begin/end. diff --git a/include/json/writer.h b/include/json/writer.h index e81c245..2b081a7 100644 --- a/include/json/writer.h +++ b/include/json/writer.h @@ -31,85 +31,93 @@ Usage: using namespace Json; void writeToStdout(StreamWriter::Factory const& factory, Value const& value) { std::unique_ptr const writer( - factory.newStreamWriter(&std::cout)); - writer->write(value); + factory.newStreamWriter()); + writer->write(value, &std::cout); std::cout << std::endl; // add lf and flush } \endcode */ class JSON_API StreamWriter { protected: - std::ostream& sout_; // not owned; will not delete + std::ostream* sout_; // not owned; will not delete public: - /// Scoped enums are not available until C++11. - struct CommentStyle { - /// Decide whether to write comments. - enum Enum { - None, ///< Drop all comments. - Most, ///< Recover odd behavior of previous versions (not implemented yet). - All ///< Keep all comments. - }; - }; - - /// Keep a reference, but do not take ownership of `sout`. - StreamWriter(std::ostream* sout); + StreamWriter(); virtual ~StreamWriter(); - /// Write Value into document as configured in sub-class. - /// \return zero on success - /// \throw std::exception possibly, depending on configuration - virtual int write(Value const& root) = 0; + /** Write Value into document as configured in sub-class. + Do not take ownership of sout, but maintain a reference during function. + \pre sout != NULL + \return zero on success + \throw std::exception possibly, depending on configuration + */ + virtual int write(Value const& root, std::ostream* sout) = 0; /** \brief A simple abstract factory. */ class JSON_API Factory { public: virtual ~Factory(); - /// Do not take ownership of sout, but maintain a reference. - virtual StreamWriter* newStreamWriter(std::ostream* sout) const = 0; + /** \brief Allocate a CharReader via operator new(). + * \throw std::exception if something goes wrong (e.g. invalid settings) + */ + virtual StreamWriter* newStreamWriter() const = 0; }; // Factory }; // StreamWriter -/// \brief Write into stringstream, then return string, for convenience. -std::string writeString(Value const& root, StreamWriter::Factory const& factory); +/** \brief Write into stringstream, then return string, for convenience. + * A StreamWriter will be created from the factory, used, and then deleted. + */ +std::string writeString(StreamWriter::Factory const& factory, Value const& root); /** \brief Build a StreamWriter implementation. - \deprecated This is experimental and will be altered before the next release. - Usage: \code using namespace Json; Value value = ...; StreamWriterBuilder builder; - builder.cs_ = StreamWriter::CommentStyle::None; - builder.indentation_ = " "; // or whatever you like - writer->write(value); + builder.settings_["commentStyle"] = "None"; + builder.settings_["indentation"] = " "; // or whatever you like + std::unique_ptr writer( + builder.newStreamWriter()); + writer->write(value, &std::cout); std::cout << std::endl; // add lf and flush \endcode */ class JSON_API StreamWriterBuilder : public StreamWriter::Factory { public: - // Note: We cannot add data-members to this class without a major version bump. - // So these might as well be completely exposed. + // Note: We use a Json::Value so that we can add data-members to this class + // without a major version bump. + /** Configuration of this builder. + Available settings (case-sensitive): + - "commentStyle": "None", "Some", or "All" + - "indentation": "" - /** \brief How to write comments. - * Default: All - */ - StreamWriter::CommentStyle::Enum cs_; - /** \brief Write in human-friendly style. - - If "", then skip all indentation and newlines. - In that case, you probably want CommentStyle::None also. - Default: "\t" - */ - std::string indentation_; + You can examine 'settings_` yourself + to see the defaults. You can also write and read them just like any + JSON Value. + \sa setDefaults() + */ + Json::Value settings_; StreamWriterBuilder(); virtual ~StreamWriterBuilder(); - /// Do not take ownership of sout, but maintain a reference. - virtual StreamWriter* newStreamWriter(std::ostream* sout) const; + /** + * \throw std::exception if something goes wrong (e.g. invalid settings) + */ + virtual StreamWriter* newStreamWriter() const; + + /** \return true if 'settings' are legal and consistent; + * otherwise, indicate bad settings via 'invalid'. + */ + bool validate(Json::Value* invalid) const; + /** Called by ctor, but you can use this to reset settings_. + * \pre 'settings' != NULL (but Json::null is fine) + * \remark Defaults: + * \snippet src/lib_json/json_writer.cpp StreamWriterBuilderDefaults + */ + static void setDefaults(Json::Value* settings); }; /** \brief Build a StreamWriter implementation. @@ -120,10 +128,12 @@ public: * \code * OldCompressingStreamWriterBuilder b; * b.dropNullPlaceHolders_ = true; // etc. - * StreamWriter* w = b.newStreamWriter(&std::cout); - * w->write(value); + * StreamWriter* w = b.newStreamWriter(); + * w->write(value, &std::cout); * delete w; * \endcode + * + * \deprecated Use StreamWriterBuilder */ class JSON_API OldCompressingStreamWriterBuilder : public StreamWriter::Factory { @@ -155,7 +165,7 @@ public: , omitEndingLineFeed_(false) , enableYAMLCompatibility_(false) {} - virtual StreamWriter* newStreamWriter(std::ostream*) const; + virtual StreamWriter* newStreamWriter() const; }; /** \brief Abstract class for writers. diff --git a/src/jsontestrunner/main.cpp b/src/jsontestrunner/main.cpp index dba943b..1ec1fb6 100644 --- a/src/jsontestrunner/main.cpp +++ b/src/jsontestrunner/main.cpp @@ -185,7 +185,7 @@ static std::string useBuiltStyledStreamWriter( Json::Value const& root) { Json::StreamWriterBuilder builder; - return writeString(root, builder); + return Json::writeString(builder, root); } static int rewriteValueTree( const std::string& rewritePath, diff --git a/src/lib_json/json_reader.cpp b/src/lib_json/json_reader.cpp index 410e793..703a5c2 100644 --- a/src/lib_json/json_reader.cpp +++ b/src/lib_json/json_reader.cpp @@ -16,6 +16,8 @@ #include #include #include +#include +#include #if defined(_MSC_VER) && _MSC_VER < 1500 // VC++ 8.0 and below #define snprintf _snprintf @@ -912,14 +914,71 @@ public: }; CharReaderBuilder::CharReaderBuilder() - : collectComments_(true) - , features_(Features::all()) -{} +{ + setDefaults(&settings_); +} CharReaderBuilder::~CharReaderBuilder() {} CharReader* CharReaderBuilder::newCharReader() const { - return new OldReader(collectComments_, features_); + if (!validate(NULL)) throw std::runtime_error("invalid settings"); + // TODO: Maybe serialize the invalid settings into the exception. + + bool collectComments = settings_["collectComments"].asBool(); + Features features = Features::all(); + features.allowComments_ = settings_["allowComments"].asBool(); + features.strictRoot_ = settings_["strictRoot"].asBool(); + features.allowDroppedNullPlaceholders_ = settings_["allowDroppedNullPlaceholders"].asBool(); + features.allowNumericKeys_ = settings_["allowNumericKeys"].asBool(); + return new OldReader(collectComments, features); +} +static void getValidReaderKeys(std::set* valid_keys) +{ + valid_keys->clear(); + valid_keys->insert("collectComments"); + valid_keys->insert("allowComments"); + valid_keys->insert("strictRoot"); + valid_keys->insert("allowDroppedNullPlaceholders"); + valid_keys->insert("allowNumericKeys"); +} +bool CharReaderBuilder::validate(Json::Value* invalid) const +{ + Json::Value my_invalid; + if (!invalid) invalid = &my_invalid; // so we do not need to test for NULL + Json::Value& inv = *invalid; + bool valid = true; + std::set valid_keys; + getValidReaderKeys(&valid_keys); + Value::Members keys = settings_.getMemberNames(); + size_t n = keys.size(); + for (size_t i = 0; i < n; ++i) { + std::string const& key = keys[i]; + if (valid_keys.find(key) == valid_keys.end()) { + inv[key] = settings_[key]; + } + } + return valid; +} +// static +void CharReaderBuilder::strictMode(Json::Value* settings) +{ +//! [CharReaderBuilderStrictMode] + (*settings)["allowComments"] = false; + (*settings)["strictRoot"] = true; + (*settings)["allowDroppedNullPlaceholders"] = false; + (*settings)["allowNumericKeys"] = false; +//! [CharReaderBuilderStrictMode] +} +// static +void CharReaderBuilder::setDefaults(Json::Value* settings) +{ +//! [CharReaderBuilderDefaults] + (*settings)["collectComments"] = true; + (*settings)["allowComments"] = true; + (*settings)["strictRoot"] = false; + (*settings)["allowDroppedNullPlaceholders"] = false; + (*settings)["allowNumericKeys"] = false; +//! [CharReaderBuilderDefaults] } ////////////////////////////////// diff --git a/src/lib_json/json_writer.cpp b/src/lib_json/json_writer.cpp index 44aa9ec..27d5f56 100644 --- a/src/lib_json/json_writer.cpp +++ b/src/lib_json/json_writer.cpp @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include #include #include @@ -674,16 +676,25 @@ bool StyledStreamWriter::hasCommentForValue(const Value& value) { ////////////////////////// // BuiltStyledStreamWriter +/// Scoped enums are not available until C++11. +struct CommentStyle { + /// Decide whether to write comments. + enum Enum { + None, ///< Drop all comments. + Most, ///< Recover odd behavior of previous versions (not implemented yet). + All ///< Keep all comments. + }; +}; + struct BuiltStyledStreamWriter : public StreamWriter { BuiltStyledStreamWriter( - std::ostream* sout, std::string const& indentation, - StreamWriter::CommentStyle::Enum cs, + CommentStyle::Enum cs, std::string const& colonSymbol, std::string const& nullSymbol, std::string const& endingLineFeedSymbol); - virtual int write(Value const& root); + virtual int write(Value const& root, std::ostream* sout); private: void writeValue(Value const& value); void writeArrayValue(Value const& value); @@ -711,14 +722,12 @@ private: bool indented_ : 1; }; BuiltStyledStreamWriter::BuiltStyledStreamWriter( - std::ostream* sout, std::string const& indentation, - StreamWriter::CommentStyle::Enum cs, + CommentStyle::Enum cs, std::string const& colonSymbol, std::string const& nullSymbol, std::string const& endingLineFeedSymbol) - : StreamWriter(sout) - , rightMargin_(74) + : rightMargin_(74) , indentation_(indentation) , cs_(cs) , colonSymbol_(colonSymbol) @@ -728,8 +737,9 @@ BuiltStyledStreamWriter::BuiltStyledStreamWriter( , indented_(false) { } -int BuiltStyledStreamWriter::write(Value const& root) +int BuiltStyledStreamWriter::write(Value const& root, std::ostream* sout) { + sout_ = sout; addChildValues_ = false; indented_ = true; indentString_ = ""; @@ -738,7 +748,8 @@ int BuiltStyledStreamWriter::write(Value const& root) indented_ = true; writeValue(root); writeCommentAfterValueOnSameLine(root); - sout_ << endingLineFeedSymbol_; + *sout_ << endingLineFeedSymbol_; + sout_ = NULL; return 0; } void BuiltStyledStreamWriter::writeValue(Value const& value) { @@ -777,13 +788,13 @@ void BuiltStyledStreamWriter::writeValue(Value const& value) { Value const& childValue = value[name]; writeCommentBeforeValue(childValue); writeWithIndent(valueToQuotedString(name.c_str())); - sout_ << colonSymbol_; + *sout_ << colonSymbol_; writeValue(childValue); if (++it == members.end()) { writeCommentAfterValueOnSameLine(childValue); break; } - sout_ << ","; + *sout_ << ","; writeCommentAfterValueOnSameLine(childValue); } unindent(); @@ -819,7 +830,7 @@ void BuiltStyledStreamWriter::writeArrayValue(Value const& value) { writeCommentAfterValueOnSameLine(childValue); break; } - sout_ << ","; + *sout_ << ","; writeCommentAfterValueOnSameLine(childValue); } unindent(); @@ -827,15 +838,15 @@ void BuiltStyledStreamWriter::writeArrayValue(Value const& value) { } else // output on a single line { assert(childValues_.size() == size); - sout_ << "["; - if (!indentation_.empty()) sout_ << " "; + *sout_ << "["; + if (!indentation_.empty()) *sout_ << " "; for (unsigned index = 0; index < size; ++index) { if (index > 0) - sout_ << ", "; - sout_ << childValues_[index]; + *sout_ << ", "; + *sout_ << childValues_[index]; } - if (!indentation_.empty()) sout_ << " "; - sout_ << "]"; + if (!indentation_.empty()) *sout_ << " "; + *sout_ << "]"; } } } @@ -872,7 +883,7 @@ void BuiltStyledStreamWriter::pushValue(std::string const& value) { if (addChildValues_) childValues_.push_back(value); else - sout_ << value; + *sout_ << value; } void BuiltStyledStreamWriter::writeIndent() { @@ -883,13 +894,13 @@ void BuiltStyledStreamWriter::writeIndent() { if (!indentation_.empty()) { // In this case, drop newlines too. - sout_ << '\n' << indentString_; + *sout_ << '\n' << indentString_; } } void BuiltStyledStreamWriter::writeWithIndent(std::string const& value) { if (!indented_) writeIndent(); - sout_ << value; + *sout_ << value; indented_ = false; } @@ -909,11 +920,11 @@ void BuiltStyledStreamWriter::writeCommentBeforeValue(Value const& root) { const std::string& comment = root.getComment(commentBefore); std::string::const_iterator iter = comment.begin(); while (iter != comment.end()) { - sout_ << *iter; + *sout_ << *iter; if (*iter == '\n' && (iter != comment.end() && *(iter + 1) == '/')) // writeIndent(); // would write extra newline - sout_ << indentString_; + *sout_ << indentString_; ++iter; } indented_ = false; @@ -922,11 +933,11 @@ void BuiltStyledStreamWriter::writeCommentBeforeValue(Value const& root) { void BuiltStyledStreamWriter::writeCommentAfterValueOnSameLine(Value const& root) { if (cs_ == CommentStyle::None) return; if (root.hasComment(commentAfterOnSameLine)) - sout_ << " " + root.getComment(commentAfterOnSameLine); + *sout_ << " " + root.getComment(commentAfterOnSameLine); if (root.hasComment(commentAfter)) { writeIndent(); - sout_ << root.getComment(commentAfter); + *sout_ << root.getComment(commentAfter); } } @@ -940,8 +951,8 @@ bool BuiltStyledStreamWriter::hasCommentForValue(const Value& value) { /////////////// // StreamWriter -StreamWriter::StreamWriter(std::ostream* sout) - : sout_(*sout) +StreamWriter::StreamWriter() + : sout_(NULL) { } StreamWriter::~StreamWriter() @@ -950,41 +961,70 @@ StreamWriter::~StreamWriter() StreamWriter::Factory::~Factory() {} StreamWriterBuilder::StreamWriterBuilder() - : cs_(StreamWriter::CommentStyle::All) - , indentation_("\t") -{} +{ + setDefaults(&settings_); +} StreamWriterBuilder::~StreamWriterBuilder() {} -StreamWriter* StreamWriterBuilder::newStreamWriter(std::ostream* stream) const +StreamWriter* StreamWriterBuilder::newStreamWriter() const { + if (!validate(NULL)) throw std::runtime_error("invalid settings"); + // TODO: Maybe serialize the invalid settings into the exception. + + std::string indentation = settings_["indentation"].asString(); + std::string cs_str = settings_["commentStyle"].asString(); + CommentStyle::Enum cs = CommentStyle::All; + if (cs_str == "All") { + cs = CommentStyle::All; + } else if (cs_str == "None") { + cs = CommentStyle::None; + } else { + return NULL; + } std::string colonSymbol = " : "; - if (indentation_.empty()) { + if (indentation.empty()) { colonSymbol = ":"; } std::string nullSymbol = "null"; std::string endingLineFeedSymbol = ""; - return new BuiltStyledStreamWriter(stream, - indentation_, cs_, + return new BuiltStyledStreamWriter( + indentation, cs, colonSymbol, nullSymbol, endingLineFeedSymbol); } -/* -// This might become public someday. -class StreamWriterBuilderFactory { -public: - virtual ~StreamWriterBuilderFactory(); - virtual StreamWriterBuilder* newStreamWriterBuilder() const; -}; -StreamWriterBuilderFactory::~StreamWriterBuilderFactory() +static void getValidWriterKeys(std::set* valid_keys) { + valid_keys->clear(); + valid_keys->insert("indentation"); + valid_keys->insert("commentStyle"); } -StreamWriterBuilder* StreamWriterBuilderFactory::newStreamWriterBuilder() const +bool StreamWriterBuilder::validate(Json::Value* invalid) const { - return new StreamWriterBuilder; + Json::Value my_invalid; + if (!invalid) invalid = &my_invalid; // so we do not need to test for NULL + Json::Value& inv = *invalid; + bool valid = true; + std::set valid_keys; + getValidWriterKeys(&valid_keys); + Value::Members keys = settings_.getMemberNames(); + size_t n = keys.size(); + for (size_t i = 0; i < n; ++i) { + std::string const& key = keys[i]; + if (valid_keys.find(key) == valid_keys.end()) { + inv[key] = settings_[key]; + } + } + return valid; +} +// static +void StreamWriterBuilder::setDefaults(Json::Value* settings) +{ + //! [StreamWriterBuilderDefaults] + (*settings)["commentStyle"] = "All"; + (*settings)["indentation"] = "\t"; + //! [StreamWriterBuilderDefaults] } -*/ -StreamWriter* OldCompressingStreamWriterBuilder::newStreamWriter( - std::ostream* stream) const +StreamWriter* OldCompressingStreamWriterBuilder::newStreamWriter() const { std::string colonSymbol = " : "; if (enableYAMLCompatibility_) { @@ -1000,22 +1040,22 @@ StreamWriter* OldCompressingStreamWriterBuilder::newStreamWriter( if (omitEndingLineFeed_) { endingLineFeedSymbol = ""; } - return new BuiltStyledStreamWriter(stream, - "", StreamWriter::CommentStyle::None, + return new BuiltStyledStreamWriter( + "", CommentStyle::None, colonSymbol, nullSymbol, endingLineFeedSymbol); } -std::string writeString(Value const& root, StreamWriter::Factory const& builder) { +std::string writeString(StreamWriter::Factory const& builder, Value const& root) { std::ostringstream sout; - StreamWriterPtr const sw(builder.newStreamWriter(&sout)); - sw->write(root); + StreamWriterPtr const writer(builder.newStreamWriter()); + writer->write(root, &sout); return sout.str(); } std::ostream& operator<<(std::ostream& sout, Value const& root) { StreamWriterBuilder builder; - StreamWriterPtr const writer(builder.newStreamWriter(&sout)); - writer->write(root); + StreamWriterPtr const writer(builder.newStreamWriter()); + writer->write(root, &sout); return sout; }