Commit c5a4926b authored by Milo Yip's avatar Milo Yip

Merge pull request #67 from miloyip/issue66writerassertion

Fix #66 by adding Writer::Reset() and Writer::IsComplete()
parents 2e0b3de8 27101d9c
...@@ -78,13 +78,13 @@ public: ...@@ -78,13 +78,13 @@ public:
bool empty = Base::level_stack_.template Pop<typename Base::Level>(1)->valueCount == 0; bool empty = Base::level_stack_.template Pop<typename Base::Level>(1)->valueCount == 0;
if (!empty) { if (!empty) {
Base::os_.Put('\n'); Base::os_->Put('\n');
WriteIndent(); WriteIndent();
} }
if (!Base::WriteEndObject()) if (!Base::WriteEndObject())
return false; return false;
if (Base::level_stack_.Empty()) // end of json text if (Base::level_stack_.Empty()) // end of json text
Base::os_.Flush(); Base::os_->Flush();
return true; return true;
} }
...@@ -101,13 +101,13 @@ public: ...@@ -101,13 +101,13 @@ public:
bool empty = Base::level_stack_.template Pop<typename Base::Level>(1)->valueCount == 0; bool empty = Base::level_stack_.template Pop<typename Base::Level>(1)->valueCount == 0;
if (!empty) { if (!empty) {
Base::os_.Put('\n'); Base::os_->Put('\n');
WriteIndent(); WriteIndent();
} }
if (!Base::WriteEndArray()) if (!Base::WriteEndArray())
return false; return false;
if (Base::level_stack_.Empty()) // end of json text if (Base::level_stack_.Empty()) // end of json text
Base::os_.Flush(); Base::os_->Flush();
return true; return true;
} }
...@@ -137,26 +137,26 @@ protected: ...@@ -137,26 +137,26 @@ protected:
if (level->inArray) { if (level->inArray) {
if (level->valueCount > 0) { if (level->valueCount > 0) {
Base::os_.Put(','); // add comma if it is not the first element in array Base::os_->Put(','); // add comma if it is not the first element in array
Base::os_.Put('\n'); Base::os_->Put('\n');
} }
else else
Base::os_.Put('\n'); Base::os_->Put('\n');
WriteIndent(); WriteIndent();
} }
else { // in object else { // in object
if (level->valueCount > 0) { if (level->valueCount > 0) {
if (level->valueCount % 2 == 0) { if (level->valueCount % 2 == 0) {
Base::os_.Put(','); Base::os_->Put(',');
Base::os_.Put('\n'); Base::os_->Put('\n');
} }
else { else {
Base::os_.Put(':'); Base::os_->Put(':');
Base::os_.Put(' '); Base::os_->Put(' ');
} }
} }
else else
Base::os_.Put('\n'); Base::os_->Put('\n');
if (level->valueCount % 2 == 0) if (level->valueCount % 2 == 0)
WriteIndent(); WriteIndent();
...@@ -165,13 +165,16 @@ protected: ...@@ -165,13 +165,16 @@ protected:
RAPIDJSON_ASSERT(type == kStringType); // if it's in object, then even number should be a name RAPIDJSON_ASSERT(type == kStringType); // if it's in object, then even number should be a name
level->valueCount++; level->valueCount++;
} }
else else {
RAPIDJSON_ASSERT(type == kObjectType || type == kArrayType); RAPIDJSON_ASSERT(type == kObjectType || type == kArrayType);
RAPIDJSON_ASSERT(!Base::hasRoot_); // Should only has one and only one root.
Base::hasRoot_ = true;
}
} }
void WriteIndent() { void WriteIndent() {
size_t count = (Base::level_stack_.GetSize() / sizeof(typename Base::Level)) * indentCharCount_; size_t count = (Base::level_stack_.GetSize() / sizeof(typename Base::Level)) * indentCharCount_;
PutN(Base::os_, indentChar_, count); PutN(*Base::os_, indentChar_, count);
} }
Ch indentChar_; Ch indentChar_;
......
...@@ -41,8 +41,41 @@ public: ...@@ -41,8 +41,41 @@ public:
\param levelDepth Initial capacity of stack. \param levelDepth Initial capacity of stack.
*/ */
Writer(OutputStream& os, Allocator* allocator = 0, size_t levelDepth = kDefaultLevelDepth) : Writer(OutputStream& os, Allocator* allocator = 0, size_t levelDepth = kDefaultLevelDepth) :
os_(os), level_stack_(allocator, levelDepth * sizeof(Level)), os_(&os), level_stack_(allocator, levelDepth * sizeof(Level)),
doublePrecision_(kDefaultDoublePrecision) {} doublePrecision_(kDefaultDoublePrecision), hasRoot_(false) {}
//! Reset the writer with a new stream.
/*!
This function reset the writer with a new stream and default settings,
in order to make a Writer object reusable for output multiple JSONs.
\param os New output stream.
\code
Writer<OutputStream> writer(os1);
writer.StartObject();
// ...
writer.EndObject();
writer.Reset(os2);
writer.StartObject();
// ...
writer.EndObject();
\endcode
*/
void Reset(OutputStream& os) {
os_ = &os;
doublePrecision_ = kDefaultDoublePrecision;
hasRoot_ = false;
level_stack_.Clear();
}
//! Checks whether the output is a complete JSON.
/*!
A complete JSON has a complete root object or array.
*/
bool IsComplete() const {
return hasRoot_ && level_stack_.Empty();
}
//! Set the number of significant digits for \c double values //! Set the number of significant digits for \c double values
/*! When writing a \c double value to the \c OutputStream, the number /*! When writing a \c double value to the \c OutputStream, the number
...@@ -103,7 +136,7 @@ public: ...@@ -103,7 +136,7 @@ public:
level_stack_.template Pop<Level>(1); level_stack_.template Pop<Level>(1);
bool ret = WriteEndObject(); bool ret = WriteEndObject();
if (level_stack_.Empty()) // end of json text if (level_stack_.Empty()) // end of json text
os_.Flush(); os_->Flush();
return ret; return ret;
} }
...@@ -120,7 +153,7 @@ public: ...@@ -120,7 +153,7 @@ public:
level_stack_.template Pop<Level>(1); level_stack_.template Pop<Level>(1);
bool ret = WriteEndArray(); bool ret = WriteEndArray();
if (level_stack_.Empty()) // end of json text if (level_stack_.Empty()) // end of json text
os_.Flush(); os_->Flush();
return ret; return ret;
} }
//@} //@}
...@@ -161,22 +194,22 @@ protected: ...@@ -161,22 +194,22 @@ protected:
static const size_t kDefaultLevelDepth = 32; static const size_t kDefaultLevelDepth = 32;
bool WriteNull() { bool WriteNull() {
os_.Put('n'); os_.Put('u'); os_.Put('l'); os_.Put('l'); return true; os_->Put('n'); os_->Put('u'); os_->Put('l'); os_->Put('l'); return true;
} }
bool WriteBool(bool b) { bool WriteBool(bool b) {
if (b) { if (b) {
os_.Put('t'); os_.Put('r'); os_.Put('u'); os_.Put('e'); os_->Put('t'); os_->Put('r'); os_->Put('u'); os_->Put('e');
} }
else { else {
os_.Put('f'); os_.Put('a'); os_.Put('l'); os_.Put('s'); os_.Put('e'); os_->Put('f'); os_->Put('a'); os_->Put('l'); os_->Put('s'); os_->Put('e');
} }
return true; return true;
} }
bool WriteInt(int i) { bool WriteInt(int i) {
if (i < 0) { if (i < 0) {
os_.Put('-'); os_->Put('-');
i = -i; i = -i;
} }
return WriteUint((unsigned)i); return WriteUint((unsigned)i);
...@@ -192,14 +225,14 @@ protected: ...@@ -192,14 +225,14 @@ protected:
do { do {
--p; --p;
os_.Put(*p); os_->Put(*p);
} while (p != buffer); } while (p != buffer);
return true; return true;
} }
bool WriteInt64(int64_t i64) { bool WriteInt64(int64_t i64) {
if (i64 < 0) { if (i64 < 0) {
os_.Put('-'); os_->Put('-');
i64 = -i64; i64 = -i64;
} }
WriteUint64((uint64_t)i64); WriteUint64((uint64_t)i64);
...@@ -216,7 +249,7 @@ protected: ...@@ -216,7 +249,7 @@ protected:
do { do {
--p; --p;
os_.Put(*p); os_->Put(*p);
} while (p != buffer); } while (p != buffer);
return true; return true;
} }
...@@ -233,7 +266,7 @@ protected: ...@@ -233,7 +266,7 @@ protected:
int ret = RAPIDJSON_SNPRINTF(buffer, sizeof(buffer), "%.*g", doublePrecision_, d); int ret = RAPIDJSON_SNPRINTF(buffer, sizeof(buffer), "%.*g", doublePrecision_, d);
RAPIDJSON_ASSERT(ret >= 1); RAPIDJSON_ASSERT(ret >= 1);
for (int i = 0; i < ret; i++) for (int i = 0; i < ret; i++)
os_.Put(buffer[i]); os_->Put(buffer[i]);
return true; return true;
} }
#undef RAPIDJSON_SNPRINTF #undef RAPIDJSON_SNPRINTF
...@@ -252,32 +285,32 @@ protected: ...@@ -252,32 +285,32 @@ protected:
#undef Z16 #undef Z16
}; };
os_.Put('\"'); os_->Put('\"');
GenericStringStream<SourceEncoding> is(str); GenericStringStream<SourceEncoding> is(str);
while (is.Tell() < length) { while (is.Tell() < length) {
const Ch c = is.Peek(); const Ch c = is.Peek();
if ((sizeof(Ch) == 1 || (unsigned)c < 256) && escape[(unsigned char)c]) { if ((sizeof(Ch) == 1 || (unsigned)c < 256) && escape[(unsigned char)c]) {
is.Take(); is.Take();
os_.Put('\\'); os_->Put('\\');
os_.Put(escape[(unsigned char)c]); os_->Put(escape[(unsigned char)c]);
if (escape[(unsigned char)c] == 'u') { if (escape[(unsigned char)c] == 'u') {
os_.Put('0'); os_->Put('0');
os_.Put('0'); os_->Put('0');
os_.Put(hexDigits[(unsigned char)c >> 4]); os_->Put(hexDigits[(unsigned char)c >> 4]);
os_.Put(hexDigits[(unsigned char)c & 0xF]); os_->Put(hexDigits[(unsigned char)c & 0xF]);
} }
} }
else else
Transcoder<SourceEncoding, TargetEncoding>::Transcode(is, os_); Transcoder<SourceEncoding, TargetEncoding>::Transcode(is, *os_);
} }
os_.Put('\"'); os_->Put('\"');
return true; return true;
} }
bool WriteStartObject() { os_.Put('{'); return true; } bool WriteStartObject() { os_->Put('{'); return true; }
bool WriteEndObject() { os_.Put('}'); return true; } bool WriteEndObject() { os_->Put('}'); return true; }
bool WriteStartArray() { os_.Put('['); return true; } bool WriteStartArray() { os_->Put('['); return true; }
bool WriteEndArray() { os_.Put(']'); return true; } bool WriteEndArray() { os_->Put(']'); return true; }
void Prefix(Type type) { void Prefix(Type type) {
(void)type; (void)type;
...@@ -285,21 +318,25 @@ protected: ...@@ -285,21 +318,25 @@ protected:
Level* level = level_stack_.template Top<Level>(); Level* level = level_stack_.template Top<Level>();
if (level->valueCount > 0) { if (level->valueCount > 0) {
if (level->inArray) if (level->inArray)
os_.Put(','); // add comma if it is not the first element in array os_->Put(','); // add comma if it is not the first element in array
else // in object else // in object
os_.Put((level->valueCount % 2 == 0) ? ',' : ':'); os_->Put((level->valueCount % 2 == 0) ? ',' : ':');
} }
if (!level->inArray && level->valueCount % 2 == 0) if (!level->inArray && level->valueCount % 2 == 0)
RAPIDJSON_ASSERT(type == kStringType); // if it's in object, then even number should be a name RAPIDJSON_ASSERT(type == kStringType); // if it's in object, then even number should be a name
level->valueCount++; level->valueCount++;
} }
else else {
RAPIDJSON_ASSERT(type == kObjectType || type == kArrayType); RAPIDJSON_ASSERT(type == kObjectType || type == kArrayType);
RAPIDJSON_ASSERT(!hasRoot_); // Should only has one and only one root.
hasRoot_ = true;
}
} }
OutputStream& os_; OutputStream* os_;
internal::Stack<Allocator> level_stack_; internal::Stack<Allocator> level_stack_;
int doublePrecision_; int doublePrecision_;
bool hasRoot_;
static const int kDefaultDoublePrecision = 6; static const int kDefaultDoublePrecision = 6;
......
...@@ -56,4 +56,22 @@ inline void TempFilename(char *filename) { ...@@ -56,4 +56,22 @@ inline void TempFilename(char *filename) {
filename[i] = filename[i + 1]; filename[i] = filename[i + 1];
} }
// Use exception for catching assert
#if _MSC_VER
#pragma warning(disable : 4127)
#endif
class AssertException : public std::exception {
public:
AssertException(const char* w) : what_(w) {}
AssertException(const AssertException& other) : what_(other.what_) {}
AssertException& operator=(const AssertException& rhs) { what_ = rhs.what_; return *this; }
virtual const char* what() const throw() { return what_; }
private:
const char* what_;
};
#define RAPIDJSON_ASSERT(x) if (!(x)) throw AssertException(RAPIDJSON_STRINGIFY(x))
#endif // UNITTEST_H_ #endif // UNITTEST_H_
#include "unittest.h" #include "unittest.h"
#include "rapidjson/document.h" #include "rapidjson/document.h"
#include "rapidjson/reader.h" #include "rapidjson/reader.h"
#include "rapidjson/writer.h" #include "rapidjson/writer.h"
...@@ -14,6 +15,7 @@ TEST(Writer, Compact) { ...@@ -14,6 +15,7 @@ TEST(Writer, Compact) {
reader.Parse<0>(s, writer); reader.Parse<0>(s, writer);
EXPECT_STREQ("{\"hello\":\"world\",\"t\":true,\"f\":false,\"n\":null,\"i\":123,\"pi\":3.1416,\"a\":[1,2,3]}", buffer.GetString()); EXPECT_STREQ("{\"hello\":\"world\",\"t\":true,\"f\":false,\"n\":null,\"i\":123,\"pi\":3.1416,\"a\":[1,2,3]}", buffer.GetString());
EXPECT_EQ(77u, buffer.GetSize()); EXPECT_EQ(77u, buffer.GetSize());
EXPECT_TRUE(writer.IsComplete());
} }
// json -> parse -> writer -> json // json -> parse -> writer -> json
...@@ -25,6 +27,7 @@ TEST(Writer, Compact) { ...@@ -25,6 +27,7 @@ TEST(Writer, Compact) {
Reader reader; \ Reader reader; \
reader.Parse<0>(s, writer); \ reader.Parse<0>(s, writer); \
EXPECT_STREQ(json, buffer.GetString()); \ EXPECT_STREQ(json, buffer.GetString()); \
EXPECT_TRUE(writer.IsComplete()); \
} }
TEST(Writer, Int) { TEST(Writer, Int) {
...@@ -80,9 +83,10 @@ TEST(Writer,DoublePrecision) { ...@@ -80,9 +83,10 @@ TEST(Writer,DoublePrecision) {
reader.Parse<0>(s, writer.SetDoublePrecision(12)); reader.Parse<0>(s, writer.SetDoublePrecision(12));
EXPECT_EQ(writer.GetDoublePrecision(), 12); EXPECT_EQ(writer.GetDoublePrecision(), 12);
EXPECT_STREQ(json, buffer.GetString()); EXPECT_STREQ(json, buffer.GetString());
buffer.Clear();
} }
{ // explicit individual double precisions { // explicit individual double precisions
buffer.Clear();
writer.Reset(buffer);
writer.SetDoublePrecision(2); writer.SetDoublePrecision(2);
writer.StartArray(); writer.StartArray();
writer.Double(1.2345, 5); writer.Double(1.2345, 5);
...@@ -93,11 +97,12 @@ TEST(Writer,DoublePrecision) { ...@@ -93,11 +97,12 @@ TEST(Writer,DoublePrecision) {
EXPECT_EQ(writer.GetDoublePrecision(), 2); EXPECT_EQ(writer.GetDoublePrecision(), 2);
EXPECT_STREQ(json, buffer.GetString()); EXPECT_STREQ(json, buffer.GetString());
buffer.Clear();
} }
{ // write with default precision (output with precision loss) { // write with default precision (output with precision loss)
Document d; Document d;
d.Parse<0>(json); d.Parse<0>(json);
buffer.Clear();
writer.Reset(buffer);
d.Accept(writer.SetDoublePrecision()); d.Accept(writer.SetDoublePrecision());
// parsed again to avoid platform-dependent floating point outputs // parsed again to avoid platform-dependent floating point outputs
...@@ -108,7 +113,6 @@ TEST(Writer,DoublePrecision) { ...@@ -108,7 +113,6 @@ TEST(Writer,DoublePrecision) {
EXPECT_DOUBLE_EQ(d[1u].GetDouble(), 1.23457); EXPECT_DOUBLE_EQ(d[1u].GetDouble(), 1.23457);
EXPECT_DOUBLE_EQ(d[2u].GetDouble(), 0.123457); EXPECT_DOUBLE_EQ(d[2u].GetDouble(), 0.123457);
EXPECT_DOUBLE_EQ(d[3u].GetDouble(), 1234570); EXPECT_DOUBLE_EQ(d[3u].GetDouble(), 1234570);
buffer.Clear();
} }
} }
...@@ -119,6 +123,7 @@ TEST(Writer, Transcode) { ...@@ -119,6 +123,7 @@ TEST(Writer, Transcode) {
Writer<StringBuffer, UTF16<>, UTF8<> > writer(buffer); Writer<StringBuffer, UTF16<>, UTF8<> > writer(buffer);
GenericReader<UTF8<>, UTF16<> > reader; GenericReader<UTF8<>, UTF16<> > reader;
reader.Parse<0>(s, writer); reader.Parse<0>(s, writer);
EXPECT_TRUE(writer.IsComplete());
EXPECT_STREQ("{\"hello\":\"world\",\"t\":true,\"f\":false,\"n\":null,\"i\":123,\"pi\":3.1416,\"a\":[1,2,3],\"dollar\":\"\x24\",\"cents\":\"\xC2\xA2\",\"euro\":\"\xE2\x82\xAC\",\"gclef\":\"\xF0\x9D\x84\x9E\"}", buffer.GetString()); EXPECT_STREQ("{\"hello\":\"world\",\"t\":true,\"f\":false,\"n\":null,\"i\":123,\"pi\":3.1416,\"a\":[1,2,3],\"dollar\":\"\x24\",\"cents\":\"\xC2\xA2\",\"euro\":\"\xE2\x82\xAC\",\"gclef\":\"\xF0\x9D\x84\x9E\"}", buffer.GetString());
} }
...@@ -160,3 +165,109 @@ TEST(Writer, OStreamWrapper) { ...@@ -160,3 +165,109 @@ TEST(Writer, OStreamWrapper) {
std::string actual = ss.str(); std::string actual = ss.str();
EXPECT_STREQ("{\"hello\":\"world\",\"t\":true,\"f\":false,\"n\":null,\"i\":123,\"pi\":3.1416,\"a\":[1,2,3]}", actual.c_str()); EXPECT_STREQ("{\"hello\":\"world\",\"t\":true,\"f\":false,\"n\":null,\"i\":123,\"pi\":3.1416,\"a\":[1,2,3]}", actual.c_str());
} }
TEST(Writer, AssertRootMustBeArrayOrObject) {
#define T(x)\
{\
StringBuffer buffer;\
Writer<StringBuffer> writer(buffer);\
ASSERT_THROW(x, AssertException);\
}
T(writer.Bool(false));
T(writer.Bool(true));
T(writer.Null());
T(writer.Int(0));
T(writer.Uint(0));
T(writer.Int64(0));
T(writer.Uint64(0));
T(writer.Double(0));
T(writer.String("foo"));
#undef T
}
TEST(Writer, AssertIncorrectObjectLevel) {
StringBuffer buffer;
Writer<StringBuffer> writer(buffer);
writer.StartObject();
writer.EndObject();
ASSERT_THROW(writer.EndObject(), AssertException);
}
TEST(Writer, AssertIncorrectArrayLevel) {
StringBuffer buffer;
Writer<StringBuffer> writer(buffer);
writer.StartArray();
writer.EndArray();
ASSERT_THROW(writer.EndArray(), AssertException);
}
TEST(Writer, AssertIncorrectEndObject) {
StringBuffer buffer;
Writer<StringBuffer> writer(buffer);
writer.StartObject();
ASSERT_THROW(writer.EndArray(), AssertException);
}
TEST(Writer, AssertIncorrectEndArray) {
StringBuffer buffer;
Writer<StringBuffer> writer(buffer);
writer.StartObject();
ASSERT_THROW(writer.EndArray(), AssertException);
}
TEST(Writer, AssertObjectKeyNotString) {
#define T(x)\
{\
StringBuffer buffer;\
Writer<StringBuffer> writer(buffer);\
writer.StartObject();\
ASSERT_THROW(x, AssertException); \
}
T(writer.Bool(false));
T(writer.Bool(true));
T(writer.Null());
T(writer.Int(0));
T(writer.Uint(0));
T(writer.Int64(0));
T(writer.Uint64(0));
T(writer.Double(0));
T(writer.StartObject());
T(writer.StartArray());
#undef T
}
TEST(Writer, AssertMultipleRoot) {
StringBuffer buffer;
Writer<StringBuffer> writer(buffer);
writer.StartObject();
writer.EndObject();
ASSERT_THROW(writer.StartObject(), AssertException);
}
TEST(Writer, RootObjectIsComplete) {
StringBuffer buffer;
Writer<StringBuffer> writer(buffer);
EXPECT_FALSE(writer.IsComplete());
writer.StartObject();
EXPECT_FALSE(writer.IsComplete());
writer.String("foo");
EXPECT_FALSE(writer.IsComplete());
writer.Int(1);
EXPECT_FALSE(writer.IsComplete());
writer.EndObject();
EXPECT_TRUE(writer.IsComplete());
}
TEST(Writer, RootArrayIsComplete) {
StringBuffer buffer;
Writer<StringBuffer> writer(buffer);
EXPECT_FALSE(writer.IsComplete());
writer.StartArray();
EXPECT_FALSE(writer.IsComplete());
writer.String("foo");
EXPECT_FALSE(writer.IsComplete());
writer.Int(1);
EXPECT_FALSE(writer.IsComplete());
writer.EndArray();
EXPECT_TRUE(writer.IsComplete());
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment