diff --git a/include/rapidjson/prettywriter.h b/include/rapidjson/prettywriter.h index f01e53e..02b9420 100644 --- a/include/rapidjson/prettywriter.h +++ b/include/rapidjson/prettywriter.h @@ -78,13 +78,13 @@ public: bool empty = Base::level_stack_.template Pop(1)->valueCount == 0; if (!empty) { - Base::os_.Put('\n'); + Base::os_->Put('\n'); WriteIndent(); } if (!Base::WriteEndObject()) return false; if (Base::level_stack_.Empty()) // end of json text - Base::os_.Flush(); + Base::os_->Flush(); return true; } @@ -101,13 +101,13 @@ public: bool empty = Base::level_stack_.template Pop(1)->valueCount == 0; if (!empty) { - Base::os_.Put('\n'); + Base::os_->Put('\n'); WriteIndent(); } if (!Base::WriteEndArray()) return false; if (Base::level_stack_.Empty()) // end of json text - Base::os_.Flush(); + Base::os_->Flush(); return true; } @@ -137,26 +137,26 @@ protected: if (level->inArray) { if (level->valueCount > 0) { - Base::os_.Put(','); // add comma if it is not the first element in array - Base::os_.Put('\n'); + Base::os_->Put(','); // add comma if it is not the first element in array + Base::os_->Put('\n'); } else - Base::os_.Put('\n'); + Base::os_->Put('\n'); WriteIndent(); } else { // in object if (level->valueCount > 0) { if (level->valueCount % 2 == 0) { - Base::os_.Put(','); - Base::os_.Put('\n'); + Base::os_->Put(','); + Base::os_->Put('\n'); } else { - Base::os_.Put(':'); - Base::os_.Put(' '); + Base::os_->Put(':'); + Base::os_->Put(' '); } } else - Base::os_.Put('\n'); + Base::os_->Put('\n'); if (level->valueCount % 2 == 0) WriteIndent(); @@ -165,13 +165,16 @@ protected: RAPIDJSON_ASSERT(type == kStringType); // if it's in object, then even number should be a name level->valueCount++; } - else + else { RAPIDJSON_ASSERT(type == kObjectType || type == kArrayType); + RAPIDJSON_ASSERT(!Base::hasRoot_); // Should only has one and only one root. + Base::hasRoot_ = true; + } } void WriteIndent() { size_t count = (Base::level_stack_.GetSize() / sizeof(typename Base::Level)) * indentCharCount_; - PutN(Base::os_, indentChar_, count); + PutN(*Base::os_, indentChar_, count); } Ch indentChar_; diff --git a/include/rapidjson/writer.h b/include/rapidjson/writer.h index f3b7567..c312b74 100644 --- a/include/rapidjson/writer.h +++ b/include/rapidjson/writer.h @@ -41,8 +41,15 @@ public: \param levelDepth Initial capacity of stack. */ Writer(OutputStream& os, Allocator* allocator = 0, size_t levelDepth = kDefaultLevelDepth) : - os_(os), level_stack_(allocator, levelDepth * sizeof(Level)), - doublePrecision_(kDefaultDoublePrecision) {} + os_(&os), level_stack_(allocator, levelDepth * sizeof(Level)), + doublePrecision_(kDefaultDoublePrecision), hasRoot_(false) {} + + void Reset(OutputStream& os) { + os_ = &os; + doublePrecision_ = kDefaultDoublePrecision; + hasRoot_ = false; + level_stack_.Clear(); + } //! Set the number of significant digits for \c double values /*! When writing a \c double value to the \c OutputStream, the number @@ -103,7 +110,7 @@ public: level_stack_.template Pop(1); bool ret = WriteEndObject(); if (level_stack_.Empty()) // end of json text - os_.Flush(); + os_->Flush(); return ret; } @@ -120,7 +127,7 @@ public: level_stack_.template Pop(1); bool ret = WriteEndArray(); if (level_stack_.Empty()) // end of json text - os_.Flush(); + os_->Flush(); return ret; } //@} @@ -161,22 +168,22 @@ protected: static const size_t kDefaultLevelDepth = 32; 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) { 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 { - 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; } bool WriteInt(int i) { if (i < 0) { - os_.Put('-'); + os_->Put('-'); i = -i; } return WriteUint((unsigned)i); @@ -192,14 +199,14 @@ protected: do { --p; - os_.Put(*p); + os_->Put(*p); } while (p != buffer); return true; } bool WriteInt64(int64_t i64) { if (i64 < 0) { - os_.Put('-'); + os_->Put('-'); i64 = -i64; } WriteUint64((uint64_t)i64); @@ -216,7 +223,7 @@ protected: do { --p; - os_.Put(*p); + os_->Put(*p); } while (p != buffer); return true; } @@ -233,7 +240,7 @@ protected: int ret = RAPIDJSON_SNPRINTF(buffer, sizeof(buffer), "%.*g", doublePrecision_, d); RAPIDJSON_ASSERT(ret >= 1); for (int i = 0; i < ret; i++) - os_.Put(buffer[i]); + os_->Put(buffer[i]); return true; } #undef RAPIDJSON_SNPRINTF @@ -252,32 +259,32 @@ protected: #undef Z16 }; - os_.Put('\"'); + os_->Put('\"'); GenericStringStream is(str); while (is.Tell() < length) { const Ch c = is.Peek(); if ((sizeof(Ch) == 1 || (unsigned)c < 256) && escape[(unsigned char)c]) { is.Take(); - os_.Put('\\'); - os_.Put(escape[(unsigned char)c]); + os_->Put('\\'); + os_->Put(escape[(unsigned char)c]); if (escape[(unsigned char)c] == 'u') { - os_.Put('0'); - os_.Put('0'); - os_.Put(hexDigits[(unsigned char)c >> 4]); - os_.Put(hexDigits[(unsigned char)c & 0xF]); + os_->Put('0'); + os_->Put('0'); + os_->Put(hexDigits[(unsigned char)c >> 4]); + os_->Put(hexDigits[(unsigned char)c & 0xF]); } } else - Transcoder::Transcode(is, os_); + Transcoder::Transcode(is, *os_); } - os_.Put('\"'); + os_->Put('\"'); return true; } - bool WriteStartObject() { os_.Put('{'); return true; } - bool WriteEndObject() { os_.Put('}'); return true; } - bool WriteStartArray() { os_.Put('['); return true; } - bool WriteEndArray() { os_.Put(']'); return true; } + bool WriteStartObject() { os_->Put('{'); return true; } + bool WriteEndObject() { os_->Put('}'); return true; } + bool WriteStartArray() { os_->Put('['); return true; } + bool WriteEndArray() { os_->Put(']'); return true; } void Prefix(Type type) { (void)type; @@ -285,21 +292,25 @@ protected: Level* level = level_stack_.template Top(); if (level->valueCount > 0) { 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 - os_.Put((level->valueCount % 2 == 0) ? ',' : ':'); + os_->Put((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 level->valueCount++; } - else + else { 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 level_stack_; int doublePrecision_; + bool hasRoot_; static const int kDefaultDoublePrecision = 6; diff --git a/test/unittest/unittest.h b/test/unittest/unittest.h index 0234319..ad87d72 100644 --- a/test/unittest/unittest.h +++ b/test/unittest/unittest.h @@ -56,4 +56,22 @@ inline void TempFilename(char *filename) { 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_ diff --git a/test/unittest/writertest.cpp b/test/unittest/writertest.cpp index be3d77b..c0e83f5 100644 --- a/test/unittest/writertest.cpp +++ b/test/unittest/writertest.cpp @@ -1,4 +1,5 @@ #include "unittest.h" + #include "rapidjson/document.h" #include "rapidjson/reader.h" #include "rapidjson/writer.h" @@ -80,9 +81,10 @@ TEST(Writer,DoublePrecision) { reader.Parse<0>(s, writer.SetDoublePrecision(12)); EXPECT_EQ(writer.GetDoublePrecision(), 12); EXPECT_STREQ(json, buffer.GetString()); - buffer.Clear(); } { // explicit individual double precisions + buffer.Clear(); + writer.Reset(buffer); writer.SetDoublePrecision(2); writer.StartArray(); writer.Double(1.2345, 5); @@ -93,11 +95,12 @@ TEST(Writer,DoublePrecision) { EXPECT_EQ(writer.GetDoublePrecision(), 2); EXPECT_STREQ(json, buffer.GetString()); - buffer.Clear(); } { // write with default precision (output with precision loss) Document d; d.Parse<0>(json); + buffer.Clear(); + writer.Reset(buffer); d.Accept(writer.SetDoublePrecision()); // parsed again to avoid platform-dependent floating point outputs @@ -108,7 +111,6 @@ TEST(Writer,DoublePrecision) { EXPECT_DOUBLE_EQ(d[1u].GetDouble(), 1.23457); EXPECT_DOUBLE_EQ(d[2u].GetDouble(), 0.123457); EXPECT_DOUBLE_EQ(d[3u].GetDouble(), 1234570); - buffer.Clear(); } } @@ -160,3 +162,81 @@ TEST(Writer, OStreamWrapper) { 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()); } + +TEST(Writer, AssertRootMustBeArrayOrObject) { +#define T(x)\ + {\ + StringBuffer buffer;\ + Writer 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 writer(buffer); + writer.StartObject(); + writer.EndObject(); + ASSERT_THROW(writer.EndObject(), AssertException); +} + +TEST(Writer, AssertIncorrectArrayLevel) { + StringBuffer buffer; + Writer writer(buffer); + writer.StartArray(); + writer.EndArray(); + ASSERT_THROW(writer.EndArray(), AssertException); +} + +TEST(Writer, AssertIncorrectEndObject) { + StringBuffer buffer; + Writer writer(buffer); + writer.StartObject(); + ASSERT_THROW(writer.EndArray(), AssertException); +} + +TEST(Writer, AssertIncorrectEndArray) { + StringBuffer buffer; + Writer writer(buffer); + writer.StartObject(); + ASSERT_THROW(writer.EndArray(), AssertException); +} + +TEST(Writer, AssertObjectKeyNotString) { +#define T(x)\ + {\ + StringBuffer buffer;\ + Writer 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 writer(buffer); + writer.StartObject(); + writer.EndObject(); + ASSERT_THROW(writer.StartObject(), AssertException); +}