Merge pull request #132 from cdunn2001/builder

StreamWriter::Builder

Deprecate old Writers, but include them in tests.

This should still be binary-compatible with 1.3.0.
This commit is contained in:
Christopher Dunn 2015-01-25 18:52:09 -06:00
commit 54b8e6939a
5 changed files with 562 additions and 23 deletions

View File

@ -73,24 +73,31 @@ for ( int index = 0; index < plugins.size(); ++index ) // Iterates over the seq
setIndentLength( root["indent"].get("length", 3).asInt() );
setIndentUseSpace( root["indent"].get("use_space", true).asBool() );
// ...
// At application shutdown to make the new configuration document:
// 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();
Json::StyledWriter writer;
// Make a new JSON document for the configuration. Preserve original comments.
std::string outputConfig = writer.write( root );
// To write into a steam with minimal memory overhead,
// create a Builder for a StreamWriter.
Json::StreamWriter::Builder builder;
builder.withIndentation(" "); // or whatever you like
// You can also use streams. This will put the contents of any JSON
// Then build a StreamWriter.
// (Of course, you can write to std::ostringstream if you prefer.)
std::shared_ptr<Json::StreamWriter> writer(
builder.newStreamWriter( &std::cout );
// Make a new JSON document for the configuration. Preserve original comments.
writer->write( root );
// If you like the defaults, you can insert directly into a stream.
std::cout << root;
// You can also read from a stream. This will put the contents of any JSON
// stream at a particular sub-value, if you'd like.
std::cin >> root["subtree"];
// And you can write to a stream, using the StyledWriter automatically.
std::cout << root;
\endcode
\section _pbuild Build instructions

View File

@ -22,8 +22,88 @@
namespace Json {
class Value;
class StreamWriterBuilder;
/**
Usage:
using namespace Json;
Value value;
StreamWriter::Builder builder;
builder.withCommentStyle(StreamWriter::CommentStyle::None);
std::shared_ptr<StreamWriter> writer(
builder.newStreamWriter(&std::cout));
writer->write(value);
std::cout.flush();
*/
class JSON_API StreamWriter {
protected:
std::ostream& sout_; // not owned; will not delete
public:
/// `All`: Keep all comments.
/// `None`: Drop all comments.
/// Use `Most` to recover the odd behavior of previous versions.
/// Only `All` is currently implemented.
enum class CommentStyle {None, Most, All};
/// Keep a reference, but do not take ownership of `sout`.
StreamWriter(std::ostream* sout);
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;
/// Because this Builder is non-virtual, we can safely add
/// methods without a major version bump.
/// \see http://stackoverflow.com/questions/14875052/pure-virtual-functions-and-binary-compatibility
class Builder {
StreamWriterBuilder* own_;
Builder(Builder const&); // noncopyable
void operator=(Builder const&); // noncopyable
public:
Builder();
~Builder(); // delete underlying StreamWriterBuilder
Builder& withCommentStyle(CommentStyle cs); /// default: All
/** \brief Write in human-friendly style.
If "", then skip all indentation, newlines, and comments,
which implies CommentStyle::None.
Default: "\t"
*/
Builder& withIndentation(std::string indentation);
/** \brief Drop the "null" string from the writer's output for nullValues.
* Strictly speaking, this is not valid JSON. But when the output is being
* fed to a browser's Javascript, it makes for smaller output and the
* browser can handle the output just fine.
*/
Builder& withDropNullPlaceholders(bool v);
/** \brief Do not add \n at end of document.
* Normally, we add an extra newline, just because.
*/
Builder& withOmitEndingLineFeed(bool v);
/** \brief Add a space after ':'.
* If indentation is non-empty, we surround colon with whitespace,
* e.g. " : "
* This will add back the trailing space when there is no indentation.
* This seems dubious when the entire document is on a single line,
* but we leave this here to repduce the behavior of the old `FastWriter`.
*/
Builder& withEnableYAMLCompatibility(bool v);
/// Do not take ownership of sout, but maintain a reference.
StreamWriter* newStreamWriter(std::ostream* sout) const;
};
};
/// \brief Write into stringstream, then return string, for convenience.
std::string writeString(Value const& root, StreamWriter::Builder const& builder);
/** \brief Abstract class for writers.
* \deprecated Use StreamWriter::Builder.
*/
class JSON_API Writer {
public:
@ -39,6 +119,7 @@ public:
*consumption,
* but may be usefull to support feature such as RPC where bandwith is limited.
* \sa Reader, Value
* \deprecated Use StreamWriter::Builder.
*/
class JSON_API FastWriter : public Writer {
public:
@ -90,6 +171,7 @@ private:
*#CommentPlacement.
*
* \sa Reader, Value, Value::setComment()
* \deprecated Use StreamWriter::Builder.
*/
class JSON_API StyledWriter : public Writer {
public:
@ -151,6 +233,7 @@ private:
*
* \param indentation Each level will be indented by this amount extra.
* \sa Reader, Value, Value::setComment()
* \deprecated Use StreamWriter::Builder.
*/
class JSON_API StyledStreamWriter {
public:

View File

@ -151,7 +151,6 @@ static int parseAndSaveValueTree(const std::string& input,
reader.getFormattedErrorMessages().c_str());
return 1;
}
if (!parseOnly) {
FILE* factual = fopen(actual.c_str(), "wt");
if (!factual) {
@ -182,6 +181,13 @@ static std::string useStyledStreamWriter(
writer.write(sout, root);
return sout.str();
}
static std::string useBuiltStyledStreamWriter(
Json::Value const& root)
{
Json::StreamWriter::Builder builder;
builder.withCommentStyle(Json::StreamWriter::CommentStyle::All);
return writeString(root, builder);
}
static int rewriteValueTree(
const std::string& rewritePath,
const Json::Value& root,
@ -248,6 +254,8 @@ static int parseCommandLine(
opts->write = &useStyledWriter;
} else if (writerName == "StyledStreamWriter") {
opts->write = &useStyledStreamWriter;
} else if (writerName == "BuiltStyledStreamWriter") {
opts->write = &useBuiltStyledStreamWriter;
} else {
printf("Unknown '--json-writer %s'\n", writerName.c_str());
return 4;

View File

@ -7,13 +7,14 @@
#include <json/writer.h>
#include "json_tool.h"
#endif // if !defined(JSON_IS_AMALGAMATION)
#include <iomanip>
#include <memory>
#include <sstream>
#include <utility>
#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <sstream>
#include <iomanip>
#include <math.h>
#if defined(_MSC_VER) && _MSC_VER < 1500 // VC++ 8.0 and below
#include <float.h>
@ -664,9 +665,442 @@ bool StyledStreamWriter::hasCommentForValue(const Value& value) {
value.hasComment(commentAfter);
}
std::ostream& operator<<(std::ostream& sout, const Value& root) {
Json::StyledStreamWriter writer;
writer.write(sout, root);
//////////////////////////
// BuiltStyledStreamWriter
struct BuiltStyledStreamWriter : public StreamWriter
{
BuiltStyledStreamWriter(
std::ostream* sout,
std::string const& indentation,
StreamWriter::CommentStyle cs,
std::string const& colonSymbol,
std::string const& nullSymbol,
std::string const& endingLineFeedSymbol);
virtual int write(Value const& root);
private:
void writeValue(Value const& value);
void writeArrayValue(Value const& value);
bool isMultineArray(Value const& value);
void pushValue(std::string const& value);
void writeIndent();
void writeWithIndent(std::string const& value);
void indent();
void unindent();
void writeCommentBeforeValue(Value const& root);
void writeCommentAfterValueOnSameLine(Value const& root);
static bool hasCommentForValue(const Value& value);
typedef std::vector<std::string> ChildValues;
ChildValues childValues_;
std::string indentString_;
int rightMargin_;
std::string indentation_;
CommentStyle cs_;
std::string colonSymbol_;
std::string nullSymbol_;
std::string endingLineFeedSymbol_;
bool addChildValues_ : 1;
bool indented_ : 1;
};
BuiltStyledStreamWriter::BuiltStyledStreamWriter(
std::ostream* sout,
std::string const& indentation,
StreamWriter::CommentStyle cs,
std::string const& colonSymbol,
std::string const& nullSymbol,
std::string const& endingLineFeedSymbol)
: StreamWriter(sout)
, rightMargin_(74)
, indentation_(indentation)
, cs_(cs)
, colonSymbol_(colonSymbol)
, nullSymbol_(nullSymbol)
, endingLineFeedSymbol_(endingLineFeedSymbol)
, addChildValues_(false)
, indented_(false)
{
}
int BuiltStyledStreamWriter::write(Value const& root)
{
addChildValues_ = false;
indented_ = true;
indentString_ = "";
writeCommentBeforeValue(root);
if (!indented_) writeIndent();
indented_ = true;
writeValue(root);
writeCommentAfterValueOnSameLine(root);
sout_ << endingLineFeedSymbol_;
return 0;
}
void BuiltStyledStreamWriter::writeValue(Value const& value) {
switch (value.type()) {
case nullValue:
pushValue(nullSymbol_);
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 (;;) {
std::string const& name = *it;
Value const& childValue = value[name];
writeCommentBeforeValue(childValue);
writeWithIndent(valueToQuotedString(name.c_str()));
sout_ << colonSymbol_;
writeValue(childValue);
if (++it == members.end()) {
writeCommentAfterValueOnSameLine(childValue);
break;
}
sout_ << ",";
writeCommentAfterValueOnSameLine(childValue);
}
unindent();
writeWithIndent("}");
}
} break;
}
}
void BuiltStyledStreamWriter::writeArrayValue(Value const& value) {
unsigned size = value.size();
if (size == 0)
pushValue("[]");
else {
bool isMultiLine = (cs_ == CommentStyle::All) || isMultineArray(value);
if (isMultiLine) {
writeWithIndent("[");
indent();
bool hasChildValue = !childValues_.empty();
unsigned index = 0;
for (;;) {
Value const& childValue = value[index];
writeCommentBeforeValue(childValue);
if (hasChildValue)
writeWithIndent(childValues_[index]);
else {
if (!indented_) writeIndent();
indented_ = true;
writeValue(childValue);
indented_ = false;
}
if (++index == size) {
writeCommentAfterValueOnSameLine(childValue);
break;
}
sout_ << ",";
writeCommentAfterValueOnSameLine(childValue);
}
unindent();
writeWithIndent("]");
} else // output on a single line
{
assert(childValues_.size() == size);
sout_ << "[";
if (!indentation_.empty()) sout_ << " ";
for (unsigned index = 0; index < size; ++index) {
if (index > 0)
sout_ << ", ";
sout_ << childValues_[index];
}
if (!indentation_.empty()) sout_ << " ";
sout_ << "]";
}
}
}
bool BuiltStyledStreamWriter::isMultineArray(Value const& value) {
int size = value.size();
bool isMultiLine = size * 3 >= rightMargin_;
childValues_.clear();
for (int index = 0; index < size && !isMultiLine; ++index) {
Value const& 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; ++index) {
if (hasCommentForValue(value[index])) {
isMultiLine = true;
}
writeValue(value[index]);
lineLength += int(childValues_[index].length());
}
addChildValues_ = false;
isMultiLine = isMultiLine || lineLength >= rightMargin_;
}
return isMultiLine;
}
void BuiltStyledStreamWriter::pushValue(std::string const& value) {
if (addChildValues_)
childValues_.push_back(value);
else
sout_ << value;
}
void BuiltStyledStreamWriter::writeIndent() {
// blep intended this to look at the so-far-written string
// to determine whether we are already indented, but
// with a stream we cannot do that. So we rely on some saved state.
// The caller checks indented_.
if (!indentation_.empty()) {
// In this case, drop newlines too.
sout_ << '\n' << indentString_;
}
}
void BuiltStyledStreamWriter::writeWithIndent(std::string const& value) {
if (!indented_) writeIndent();
sout_ << value;
indented_ = false;
}
void BuiltStyledStreamWriter::indent() { indentString_ += indentation_; }
void BuiltStyledStreamWriter::unindent() {
assert(indentString_.size() >= indentation_.size());
indentString_.resize(indentString_.size() - indentation_.size());
}
void BuiltStyledStreamWriter::writeCommentBeforeValue(Value const& root) {
if (cs_ == CommentStyle::None) return;
if (!root.hasComment(commentBefore))
return;
if (!indented_) writeIndent();
const std::string& comment = root.getComment(commentBefore);
std::string::const_iterator iter = comment.begin();
while (iter != comment.end()) {
sout_ << *iter;
if (*iter == '\n' &&
(iter != comment.end() && *(iter + 1) == '/'))
// writeIndent(); // would write extra newline
sout_ << indentString_;
++iter;
}
indented_ = false;
}
void BuiltStyledStreamWriter::writeCommentAfterValueOnSameLine(Value const& root) {
if (cs_ == CommentStyle::None) return;
if (root.hasComment(commentAfterOnSameLine))
sout_ << " " + root.getComment(commentAfterOnSameLine);
if (root.hasComment(commentAfter)) {
writeIndent();
sout_ << root.getComment(commentAfter);
}
}
// static
bool BuiltStyledStreamWriter::hasCommentForValue(const Value& value) {
return value.hasComment(commentBefore) ||
value.hasComment(commentAfterOnSameLine) ||
value.hasComment(commentAfter);
}
///////////////
// StreamWriter
StreamWriter::StreamWriter(std::ostream* sout)
: sout_(*sout)
{
}
StreamWriter::~StreamWriter()
{
}
struct MyStreamWriter : public StreamWriter {
public:
MyStreamWriter(std::ostream* sout);
virtual ~MyStreamWriter();
virtual int write(Value const& root) = 0;
};
MyStreamWriter::MyStreamWriter(std::ostream* sout)
: StreamWriter(sout)
{
}
MyStreamWriter::~MyStreamWriter()
{
}
int MyStreamWriter::write(Value const& root)
{
sout_ << root;
return 0;
}
class StreamWriterBuilder {
typedef StreamWriter::CommentStyle CommentStyle;
CommentStyle cs_;
std::string indentation_;
bool dropNullPlaceholders_;
bool omitEndingLineFeed_;
bool enableYAMLCompatibility_;
public:
StreamWriterBuilder();
virtual ~StreamWriterBuilder();
virtual void setCommentStyle(CommentStyle cs);
virtual void setIndentation(std::string indentation);
virtual void setDropNullPlaceholders(bool v);
virtual void setOmitEndingLineFeed(bool v);
virtual void setEnableYAMLCompatibility(bool v);
virtual StreamWriter* newStreamWriter(std::ostream* sout) const;
};
StreamWriterBuilder::StreamWriterBuilder()
: cs_(CommentStyle::All)
, indentation_("\t")
, dropNullPlaceholders_(false)
, omitEndingLineFeed_(false)
, enableYAMLCompatibility_(false)
{
}
StreamWriterBuilder::~StreamWriterBuilder()
{
}
void StreamWriterBuilder::setCommentStyle(CommentStyle v)
{
cs_ = v;
}
void StreamWriterBuilder::setIndentation(std::string v)
{
indentation_ = v;
if (indentation_.empty()) cs_ = CommentStyle::None;
}
void StreamWriterBuilder::setDropNullPlaceholders(bool v)
{
dropNullPlaceholders_ = v;
}
void StreamWriterBuilder::setOmitEndingLineFeed(bool v)
{
omitEndingLineFeed_ = v;
}
void StreamWriterBuilder::setEnableYAMLCompatibility(bool v)
{
enableYAMLCompatibility_ = v;
}
StreamWriter* StreamWriterBuilder::newStreamWriter(std::ostream* stream) const
{
std::string colonSymbol = " : ";
if (indentation_.empty()) {
if (enableYAMLCompatibility_) {
colonSymbol = ": ";
} else {
colonSymbol = ":";
}
}
std::string nullSymbol = "null";
if (dropNullPlaceholders_) {
nullSymbol = "";
}
std::string endingLineFeedSymbol = "\n";
if (omitEndingLineFeed_) {
endingLineFeedSymbol = "";
}
return new BuiltStyledStreamWriter(stream,
indentation_, cs_,
colonSymbol, nullSymbol, endingLineFeedSymbol);
}
// This might become public someday.
class StreamWriterBuilderFactory {
public:
virtual ~StreamWriterBuilderFactory();
virtual StreamWriterBuilder* newStreamWriterBuilder() const;
};
StreamWriterBuilderFactory::~StreamWriterBuilderFactory()
{
}
StreamWriterBuilder* StreamWriterBuilderFactory::newStreamWriterBuilder() const
{
return new StreamWriterBuilder;
}
StreamWriter::Builder::Builder()
: own_(StreamWriterBuilderFactory().newStreamWriterBuilder())
{
}
StreamWriter::Builder::~Builder()
{
delete own_;
}
StreamWriter::Builder::Builder(Builder const&)
: own_(nullptr)
{abort();}
void StreamWriter::Builder::operator=(Builder const&)
{abort();}
StreamWriter::Builder& StreamWriter::Builder::withCommentStyle(CommentStyle v)
{
own_->setCommentStyle(v);
return *this;
}
StreamWriter::Builder& StreamWriter::Builder::withIndentation(std::string v)
{
own_->setIndentation(v);
return *this;
}
StreamWriter::Builder& StreamWriter::Builder::withDropNullPlaceholders(bool v)
{
own_->setDropNullPlaceholders(v);
return *this;
}
StreamWriter::Builder& StreamWriter::Builder::withOmitEndingLineFeed(bool v)
{
own_->setOmitEndingLineFeed(v);
return *this;
}
StreamWriter::Builder& StreamWriter::Builder::withEnableYAMLCompatibility(bool v)
{
own_->setEnableYAMLCompatibility(v);
return *this;
}
StreamWriter* StreamWriter::Builder::newStreamWriter(std::ostream* sout) const
{
return own_->newStreamWriter(sout);
}
std::string writeString(Value const& root, StreamWriter::Builder const& builder) {
std::ostringstream sout;
std::unique_ptr<StreamWriter> const sw(builder.newStreamWriter(&sout));
sw->write(root);
return sout.str();
}
std::ostream& operator<<(std::ostream& sout, Value const& root) {
StreamWriter::Builder builder;
builder.withCommentStyle(StreamWriter::CommentStyle::All);
builder.withIndentation("\t");
std::shared_ptr<StreamWriter> writer(builder.newStreamWriter(&sout));
writer->write(root);
return sout;
}

View File

@ -147,16 +147,23 @@ def main():
else:
input_path = None
status = runAllTests(jsontest_executable_path, input_path,
use_valgrind=options.valgrind,
with_json_checker=options.with_json_checker,
writerClass='StyledWriter')
use_valgrind=options.valgrind,
with_json_checker=options.with_json_checker,
writerClass='StyledWriter')
if status:
sys.exit(status)
status = runAllTests(jsontest_executable_path, input_path,
use_valgrind=options.valgrind,
with_json_checker=options.with_json_checker,
writerClass='StyledStreamWriter')
sys.exit(status)
use_valgrind=options.valgrind,
with_json_checker=options.with_json_checker,
writerClass='StyledStreamWriter')
if status:
sys.exit(status)
status = runAllTests(jsontest_executable_path, input_path,
use_valgrind=options.valgrind,
with_json_checker=options.with_json_checker,
writerClass='BuiltStyledStreamWriter')
if status:
sys.exit(status)
if __name__ == '__main__':
main()