diff --git a/.travis.yml b/.travis.yml index efa96e5..c911843 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,8 @@ before_script: - cd "${TRAVIS_BUILD_DIR}" script: - - make -C build/gmake -f test.make unittest + - make -C build/gmake -f test.make + - make -C build/gmake -f example.make - cd bin - ./unittest_${config_suffix} + - ./perftest_${config_suffix} diff --git a/build/premake4.lua b/build/premake4.lua index b4fdf8d..05650d8 100644 --- a/build/premake4.lua +++ b/build/premake4.lua @@ -149,8 +149,8 @@ solution "example" configuration "vs*" defines { "_CRT_SECURE_NO_WARNINGS" } - configuration "gmake" - buildoptions "-Weverything" +-- configuration "gmake" +-- buildoptions "-Weverything" project "condense" kind "ConsoleApp" @@ -176,3 +176,8 @@ solution "example" kind "ConsoleApp" files "../example/serialize/*" setTargetObjDir("../bin") + + project "simpledom" + kind "ConsoleApp" + files "../example/simpledom/*" + setTargetObjDir("../bin") diff --git a/doc/diagram/makefile b/doc/diagram/makefile new file mode 100644 index 0000000..4699438 --- /dev/null +++ b/doc/diagram/makefile @@ -0,0 +1,8 @@ +%.pdf: %.dot + dot $< -Tpdf -o $@ + +%.png: %.dot + dot $< -Tpng -o $@ + +DOTFILES = $(basename $(wildcard *.dot)) +all: $(addsuffix .png, $(DOTFILES)) #$(addsuffix .pdf, $(DOTFILES)) diff --git a/doc/diagram/simpledom.dot b/doc/diagram/simpledom.dot new file mode 100644 index 0000000..1e9edb5 --- /dev/null +++ b/doc/diagram/simpledom.dot @@ -0,0 +1,54 @@ +digraph { + compound=true + fontname="Inconsolata, Consolas" + fontsize=10 + margin="0,0" + ranksep=0.2 + penwidth=0.5 + + node [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] + edge [fontname="Inconsolata, Consolas", fontsize=10, arrowhead=normal] + + { + node [shape=record, fontsize="8", margin="0.04", height=0.2, color=gray] + srcjson [label="\{|p|r|o|j|e|c|t|\"|:|\"|r|a|p|i|d|j|s|o|n|\"|,|\"|s|t|a|r|s|\"|:|1|0|\}"] + dstjson [label="\{|p|r|o|j|e|c|t|\"|:|\"|r|a|p|i|d|j|s|o|n|\"|,|\"|s|t|a|r|s|\"|:|1|1|\}"] + } + + { + node [shape="box", style="filled", fillcolor="gray95"] + Document2 [label="(Modified) Document"] + Writer + } + + subgraph cluster1 { + margin="10,10" + labeljust="left" + label = "Document" + style=filled + fillcolor=gray95 + node [shape=Mrecord, style=filled, colorscheme=spectral7] + + root [label="{object|}", fillcolor=3] + + { + project [label="{string|\"project\"}", fillcolor=5] + rapidjson [label="{string|\"rapidjson\"}", fillcolor=5] + stars [label="{string|\"stars\"}", fillcolor=5] + ten [label="{number|10}", fillcolor=6] + } + + edge [arrowhead=vee] + root -> { project, stars } + + edge [arrowhead="none"] + project -> rapidjson + stars -> ten + } + + srcjson -> root [label=" Parse()", lhead="cluster1"] + + ten -> Document2 [label=" Increase \"stars\"", ltail="cluster1" ] + Document2 -> Writer [label=" Traverse DOM by Accept()"] + Writer -> dstjson [label=" Output to StringBuffer"] +} \ No newline at end of file diff --git a/doc/diagram/simpledom.png b/doc/diagram/simpledom.png new file mode 100644 index 0000000..dd9e9ae Binary files /dev/null and b/doc/diagram/simpledom.png differ diff --git a/doc/features.md b/doc/features.md new file mode 100644 index 0000000..7a52030 --- /dev/null +++ b/doc/features.md @@ -0,0 +1,79 @@ +# RapidJSON Features + +## General + +* Cross-platform + * Compilers: Visual Studio, gcc, clang, etc. + * Architectures: x86, x64, ARM, etc. + * Operating systems: Windows, Mac OS X, Linux, iOS, Android, etc. +* Easy installation + * Header files only library. Just copy the headers to your project. +* Self-contained, minimal dependences + * No STL, BOOST, etc. + * Only included ``, ``, ``, ``, ``, ``. * +* Without C++ exception, RTTI +* High performance + * Use template and inline functions to reduce function call overheads. + * Optional SSE2/SSE4.1 support. + +## Standard compliance + +* RapidJSON should be fully RFC4627/ECMA-404 compliance. +* Support unicode surrogate. +* Support null character (`"\u0000"`) + * For example, `["Hello\u0000World"]` can be parsed and handled gracefully. There is API for getting/setting lengths of string. + +## Unicode + +* Support UTF-8, UTF-16, UTF-32 encodings, including little endian and big endian. + * These encodings are used in input/output streams and in-memory representation. +* Support automatic detection of encodings in input stream. +* Support transcoding between encodings internally. + * For example, you can read a UTF-8 file and let RapidJSON transcode the JSON strings into UTF-16 in the DOM. +* Support encoding validation internally. + * For example, you can read a UTF-8 file, and let RapidJSON check whether all JSON strings are valid UTF-8 byte sequence. +* Support custom character types. + * By default the character types are `char` for UTF8, `wchar_t` for UTF16, `uint32_t` for UTF32. +* Support custom encodings. + +## API styles + +* SAX (Simple API for XML) style API + * Similar to [SAX](http://en.wikipedia.org/wiki/Simple_API_for_XML), RapidJSON provides a event sequential access parser API (`GenericReader`). It also provides a generator API (`GenericWriter`) which consumes the same set of events. +* DOM (Document Object Model) style API + * Similar to [DOM](http://en.wikipedia.org/wiki/Document_Object_Model) for HTML/XML, RapidJSON can parse JSON into a DOM representation (`GenericDocument`), for easy manipulation, and finally stringify back to JSON if needed. + * The DOM style API (`GenericDocument`) is actually implemented with SAX style API (`GenericReader`). SAX is faster but sometimes DOM is easier. Users can pick their choices according to scenarios. + +## DOM (Document) + +* Support insitu parsing. + * Parse JSON string values in-place at the source JSON, and then the DOM points to addresses of those strings. + * Faster than convention parsing: no allocation for strings, no copy (if string does not contain escapes), cache-friendly. +* Support 32-bit/64-bit signed/unsigned integer and `double` for JSON number type. + * RapidJSON checks range of numerical values for conversions. + +## SAX (Reader) + +* Support comprehensive error code if parsing failed. +* Support localizable error message. + +## SAX (Writer) + +* Support PrettyWriter for adding newlines and indentations. +* Support custom precision for floating point values. + +## Stream + +* Support `GenericStringBuffer` for storing the output JSON as string. +* Support `FileReadStream`/`FileWriteStream` for input/output `FILE` object. +* Support custom streams. + +## Memory + +* Minimize memory overheads for DOM. + * Each JSON value occupies exactly 16/20 bytes for most 32/64-bit machines (excluding text string). +* Support fast default allocator. + * A stack-based allocator (allocate sequentially, prohibit to free individual allocations, suitable for parsing). + * User can provide a pre-allocated buffer. (Possible to parse a number of JSONs without any CRT allocation) +* Support standard CRT(C-runtime) allocator. +* Support custom allocators. diff --git a/example/condense/condense.cpp b/example/condense/condense.cpp index ae6d36a..bad3c91 100644 --- a/example/condense/condense.cpp +++ b/example/condense/condense.cpp @@ -23,7 +23,7 @@ int main(int, char*[]) { Writer writer(os); // JSON reader parse from the input stream and let writer generate the output. - if (!reader.Parse<0>(is, writer)) { + if (!reader.Parse(is, writer)) { fprintf(stderr, "\nError(%u): %s\n", (unsigned)reader.GetErrorOffset(), GetParseError_En(reader.GetParseErrorCode())); return 1; } diff --git a/example/simpledom/simpledom.cpp b/example/simpledom/simpledom.cpp new file mode 100644 index 0000000..a2c9121 --- /dev/null +++ b/example/simpledom/simpledom.cpp @@ -0,0 +1,29 @@ +// JSON simple example +// This example does not handle errors. + +#include "rapidjson/document.h" +#include "rapidjson/writer.h" +#include "rapidjson/stringbuffer.h" +#include + +using namespace rapidjson; + +int main() { + // 1. Parse a JSON string into DOM. + const char* json = "{\"project\":\"rapidjson\",\"stars\":10}"; + Document d; + d.Parse(json); + + // 2. Modify it by DOM. + Value& s = d["stars"]; + s.SetInt(s.GetInt() + 1); + + // 3. Stringify the DOM + StringBuffer buffer; + Writer writer(buffer); + d.Accept(writer); + + // Output {"project":"rapidjson","stars":11} + std::cout << buffer.GetString() << std::endl; + return 0; +} diff --git a/example/tutorial/tutorial.cpp b/example/tutorial/tutorial.cpp index a765e01..592543e 100644 --- a/example/tutorial/tutorial.cpp +++ b/example/tutorial/tutorial.cpp @@ -19,14 +19,14 @@ int main(int, char*[]) { #if 0 // "normal" parsing, decode strings to new buffers. Can use other input stream via ParseStream(). - if (document.Parse<0>(json).HasParseError()) + if (document.Parse(json).HasParseError()) return 1; #else // In-situ parsing, decode strings directly in the source string. Source must be string. { char buffer[sizeof(json)]; memcpy(buffer, json, sizeof(json)); - if (document.ParseInsitu<0>(buffer).HasParseError()) + if (document.ParseInsitu(buffer).HasParseError()) return 1; } #endif diff --git a/include/rapidjson/document.h b/include/rapidjson/document.h index a5e718f..dc3533f 100644 --- a/include/rapidjson/document.h +++ b/include/rapidjson/document.h @@ -202,7 +202,7 @@ public: */ template GenericValue& CopyFrom(const GenericValue& rhs, Allocator& allocator) { - RAPIDJSON_ASSERT((void*)this != (void*)&rhs); + RAPIDJSON_ASSERT((void*)this != (void const*)&rhs); this->~GenericValue(); new (this) GenericValue(rhs,allocator); return *this; @@ -818,6 +818,11 @@ public: return ParseStream(is); } + template + GenericDocument& ParseStream(InputStream& is) { + return ParseStream<0, Encoding, InputStream>(is); + } + //! Parse JSON text from a mutable string. /*! \tparam parseFlags Combination of ParseFlag. \param str Mutable zero-terminated string to be parsed. @@ -834,6 +839,10 @@ public: return ParseInsitu(str); } + GenericDocument& ParseInsitu(Ch* str) { + return ParseInsitu<0, Encoding>(str); + } + //! Parse JSON text from a read-only string. /*! \tparam parseFlags Combination of ParseFlag (must not contain kParseInsituFlag). \param str Read-only zero-terminated string to be parsed. @@ -850,6 +859,10 @@ public: return Parse(str); } + GenericDocument& Parse(const Ch* str) { + return Parse<0>(str); + } + //! Whether a parse error was occured in the last parsing. bool HasParseError() const { return parseErrorCode_ != kParseErrorNone; } diff --git a/include/rapidjson/filereadstream.h b/include/rapidjson/filereadstream.h index 4d42d38..29ab393 100644 --- a/include/rapidjson/filereadstream.h +++ b/include/rapidjson/filereadstream.h @@ -69,6 +69,11 @@ private: bool eof_; }; +template<> +struct StreamTraits { + typedef FileReadStream StreamCopyType; // Enable stream copy optimization. +}; + } // namespace rapidjson #endif // RAPIDJSON_FILESTREAM_H_ diff --git a/include/rapidjson/rapidjson.h b/include/rapidjson/rapidjson.h index fd31aef..29fe0ee 100644 --- a/include/rapidjson/rapidjson.h +++ b/include/rapidjson/rapidjson.h @@ -193,6 +193,24 @@ concept Stream { \endcode */ +//! Provides additional information for stream. +/*! + By using traits pattern, this type provides a default configuration for stream. + For custom stream, this type can be specialized for other configuration. + See TEST(Reader, CustomStringStream) in readertest.cpp for example. +*/ +template +struct StreamTraits { + //! Whether to make local copy of stream for optimization during parsing. + /*! + If it is defined as Stream&, it will not make a local copy. + If it is defined as Stream, it will make a local copy for optimization. + By default, for safety, streams do not use local copy optimization, i.e. it is defined as Stream&. + Stream that can be copied fast should specialize this, like StreamTraits. + */ + typedef Stream& StreamCopyType; +}; + //! Put N copies of a character to a stream. template inline void PutN(Stream& stream, Ch c, size_t n) { @@ -225,6 +243,11 @@ struct GenericStringStream { const Ch* head_; //!< Original head of the string. }; +template +struct StreamTraits > { + typedef GenericStringStream StreamCopyType; // Enable stream copy optimization. +}; + typedef GenericStringStream > StringStream; /////////////////////////////////////////////////////////////////////////////// @@ -256,6 +279,11 @@ struct GenericInsituStringStream { Ch* head_; }; +template +struct StreamTraits > { + typedef GenericInsituStringStream StreamCopyType; // Enable stream copy optimization. +}; + typedef GenericInsituStringStream > InsituStringStream; /////////////////////////////////////////////////////////////////////////////// diff --git a/include/rapidjson/reader.h b/include/rapidjson/reader.h index 7c60f3c..a265605 100644 --- a/include/rapidjson/reader.h +++ b/include/rapidjson/reader.h @@ -135,7 +135,7 @@ struct BaseReaderHandler { */ template void SkipWhitespace(InputStream& is) { - InputStream s = is; // Use a local copy for optimization + typename StreamTraits::StreamCopyType s = is; // Use a local copy for optimization while (s.Peek() == ' ' || s.Peek() == '\n' || s.Peek() == '\r' || s.Peek() == '\t') s.Take(); is = s; @@ -283,8 +283,15 @@ public: return !HasParseError(); } + template + bool Parse(InputStream& is, Handler& handler) { + return Parse<0>(is, handler); + } + bool HasParseError() const { return parseErrorCode_ != kParseErrorNone; } + ParseErrorCode GetParseErrorCode() const { return parseErrorCode_; } + size_t GetErrorOffset() const { return errorOffset_; } private: @@ -399,7 +406,7 @@ private: // Helper function to parse four hexidecimal digits in \uXXXX in ParseString(). template unsigned ParseHex4(InputStream& is) { - InputStream s = is; // Use a local copy for optimization + typename StreamTraits::StreamCopyType s = is; // Use a local copy for optimization unsigned codepoint = 0; for (int i = 0; i < 4; i++) { Ch c = s.Take(); @@ -440,7 +447,7 @@ private: // Parse string and generate String event. Different code paths for kParseInsituFlag. template void ParseString(InputStream& is, Handler& handler) { - InputStream s = is; // Local copy for optimization + typename StreamTraits::StreamCopyType s = is; // Local copy for optimization if (parseFlags & kParseInsituFlag) { Ch *head = s.PutBegin(); ParseStringToStream(s, s); @@ -520,7 +527,7 @@ private: template void ParseNumber(InputStream& is, Handler& handler) { - InputStream s = is; // Local copy for optimization + typename StreamTraits::StreamCopyType s = is; // Local copy for optimization // Parse minus bool minus = false; if (s.Peek() == '-') { diff --git a/readme.md b/readme.md index 9bf6a16..94a9290 100644 --- a/readme.md +++ b/readme.md @@ -16,7 +16,9 @@ Rapidjson is a JSON parser and generator for C++. It was inspired by [rapidxml]( * Rapidjson is memory friendly. Each JSON value occupies exactly 16/20 bytes for most 32/64-bit machines (excluding text string). By default it uses a fast memory allocator, and the parser allocates memory compactly during parsing. -For the full features please refer to the user guide. +* Rapidjson is Unicode friendly. It supports UTF-8, UTF-16, UTF-32 (LE & BE), and their detection, validataton and transcoding internally. For example, you can read a UTF-8 file and let rapidjson transcode the JSON strings into UTF-16 in the DOM. It also supports surrogates and "\u0000" (null character). + +More features can be read [here](doc/features.md). JSON(JavaScript Object Notation) is a light-weight data exchange format. Rapidjson should be in fully compliance with RFC4627/ECMA-404. More information about JSON can be obtained at * [Introducing JSON](http://json.org/) @@ -39,9 +41,51 @@ Rapidjson is a header-only C++ library. Just copy the `rapidjson/include/rapidjs To build the tests and examples: -1. Obtain [premake4] (http://industriousone.com/premake/download). +1. Obtain [premake4](http://industriousone.com/premake/download). 2. Copy premake4 executable to rapidjson/build (or system path) 3. Run `rapidjson/build/premake.bat` on Windows, `rapidjson/build/premake.sh` on Linux or other platforms 4. On Windows, build the solution at `rapidjson/build/vs2008/` or `/vs2010/` 5. On other platforms, run GNU make at `rapidjson/build/gmake/` (e.g., `make -f test.make config=release32`, `make -f example.make config=debug32`) 6. On success, the executable are generated at `rapidjson/bin` + +## Usage at a glance + +This simple example parses a JSON string into a document (DOM), make a simple modification of the DOM, and finally stringify the DOM to a JSON string. + +```cpp +// example/simpledom/simpledom.cpp +#include "rapidjson/document.h" +#include "rapidjson/writer.h" +#include "rapidjson/stringbuffer.h" +#include + +using namespace rapidjson; + +int main() { + // 1. Parse a JSON string into DOM. + const char* json = "{\"project\":\"rapidjson\",\"stars\":10}"; + Document d; + d.Parse(json); + + // 2. Modify it by DOM. + Value& s = d["stars"]; + s.SetInt(s.GetInt() + 1); + + // 3. Stringify the DOM + StringBuffer buffer; + Writer writer(buffer); + d.Accept(writer); + + // Output {"project":"rapidjson","stars":11} + std::cout << buffer.GetString() << std::endl; + return 0; +} +``` + +Note that this exmample did not handle potential errors. + +The following diagram shows the process. + +![simpledom](doc/diagram/simpledom.png?raw=true) + +More [examples](example/) are avaliable. diff --git a/test/perftest/rapidjsontest.cpp b/test/perftest/rapidjsontest.cpp index 15e5912..db54d9b 100644 --- a/test/perftest/rapidjsontest.cpp +++ b/test/perftest/rapidjsontest.cpp @@ -28,7 +28,7 @@ public: temp_ = (char *)malloc(length_ + 1); // Parse as a document - EXPECT_FALSE(doc_.Parse<0>(json_).IsNull()); + EXPECT_FALSE(doc_.Parse(json_).IsNull()); } virtual void TearDown() { @@ -66,7 +66,7 @@ TEST_F(RapidJson, SIMD_SUFFIX(ReaderParse_DummyHandler)) { StringStream s(json_); BaseReaderHandler<> h; Reader reader; - EXPECT_TRUE(reader.Parse<0>(s, h)); + EXPECT_TRUE(reader.Parse(s, h)); } } @@ -88,7 +88,7 @@ TEST_F(RapidJson, SIMD_SUFFIX(DoucmentParseInsitu_MemoryPoolAllocator)) { //MemoryPoolAllocator<> allocator(userBuffer, userBufferSize); //Document doc(&allocator); Document doc; - doc.ParseInsitu<0>(temp_); + doc.ParseInsitu(temp_); ASSERT_TRUE(doc.IsObject()); //if (i == 0) { // size_t size = doc.GetAllocator().Size(); @@ -110,7 +110,7 @@ TEST_F(RapidJson, SIMD_SUFFIX(DoucmentParse_MemoryPoolAllocator)) { //MemoryPoolAllocator<> allocator(userBuffer, userBufferSize); //Document doc(&allocator); Document doc; - doc.Parse<0>(json_); + doc.Parse(json_); ASSERT_TRUE(doc.IsObject()); //if (i == 0) { // size_t size = doc.GetAllocator().Size(); @@ -128,7 +128,7 @@ TEST_F(RapidJson, SIMD_SUFFIX(DoucmentParse_CrtAllocator)) { for (size_t i = 0; i < kTrialCount; i++) { memcpy(temp_, json_, length_ + 1); GenericDocument, CrtAllocator> doc; - doc.Parse<0>(temp_); + doc.Parse(temp_); ASSERT_TRUE(doc.IsObject()); } } @@ -234,7 +234,7 @@ TEST_F(RapidJson, internal_Pow10) { TEST_F(RapidJson, SIMD_SUFFIX(Whitespace)) { for (size_t i = 0; i < kTrialCount; i++) { Document doc; - ASSERT_TRUE(doc.Parse<0>(whitespace_).IsArray()); + ASSERT_TRUE(doc.Parse(whitespace_).IsArray()); } } @@ -279,7 +279,7 @@ TEST_F(RapidJson, SIMD_SUFFIX(ReaderParse_DummyHandler_FileReadStream)) { FileReadStream s(fp, buffer, sizeof(buffer)); BaseReaderHandler<> h; Reader reader; - reader.Parse<0>(s, h); + reader.Parse(s, h); fclose(fp); } } diff --git a/test/unittest/documenttest.cpp b/test/unittest/documenttest.cpp index 82fc67d..e33ac0e 100644 --- a/test/unittest/documenttest.cpp +++ b/test/unittest/documenttest.cpp @@ -8,7 +8,7 @@ using namespace rapidjson; TEST(Document, Parse) { Document doc; - doc.Parse<0>(" { \"hello\" : \"world\", \"t\" : true , \"f\" : false, \"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3, 4] } "); + doc.Parse(" { \"hello\" : \"world\", \"t\" : true , \"f\" : false, \"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3, 4] } "); EXPECT_TRUE(doc.IsObject()); @@ -59,7 +59,7 @@ struct OutputStringStream : public std::ostringstream { TEST(Document, AcceptWriter) { Document doc; - doc.Parse<0>(" { \"hello\" : \"world\", \"t\" : true , \"f\" : false, \"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3, 4] } "); + doc.Parse(" { \"hello\" : \"world\", \"t\" : true , \"f\" : false, \"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3, 4] } "); OutputStringStream os; Writer writer(os); diff --git a/test/unittest/jsoncheckertest.cpp b/test/unittest/jsoncheckertest.cpp index 5c53865..7033856 100644 --- a/test/unittest/jsoncheckertest.cpp +++ b/test/unittest/jsoncheckertest.cpp @@ -42,7 +42,7 @@ TEST(JsonChecker, Reader) { } GenericDocument, CrtAllocator> document; // Use Crt allocator to check exception-safety (no memory leak) - if (!document.Parse<0>((const char*)json).HasParseError()) + if (!document.Parse((const char*)json).HasParseError()) FAIL(); //printf("%s(%u):%s\n", filename, (unsigned)document.GetErrorOffset(), document.GetParseError()); free(json); @@ -63,7 +63,7 @@ TEST(JsonChecker, Reader) { } GenericDocument, CrtAllocator> document; // Use Crt allocator to check exception-safety (no memory leak) - document.Parse<0>((const char*)json); + document.Parse((const char*)json); EXPECT_TRUE(!document.HasParseError()); free(json); } diff --git a/test/unittest/readertest.cpp b/test/unittest/readertest.cpp index 06b694e..829837d 100644 --- a/test/unittest/readertest.cpp +++ b/test/unittest/readertest.cpp @@ -603,3 +603,51 @@ TEST(Reader, SkipWhitespace) { EXPECT_EQ(expected[i], ss.Take()); } } + +// Test implementing a stream without copy stream optimization. +// Clone from GenericStringStream except that copy constructor is disabled. +template +class CustomStringStream { +public: + typedef typename Encoding::Ch Ch; + + CustomStringStream(const Ch *src) : src_(src), head_(src) {} + + Ch Peek() const { return *src_; } + Ch Take() { return *src_++; } + size_t Tell() const { return static_cast(src_ - head_); } + + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + // Not support copy constructor. + CustomStringStream(const CustomStringStream&); + + const Ch* src_; //!< Current read position. + const Ch* head_; //!< Original head of the string. +}; + +// If the following code is compiled, it should generate compilation error as predicted. +// Because CustomStringStream<> is not copyable via making copy constructor private. +#if 0 +namespace rapidjson { + +template +struct StreamTraits > { + typedef CustomStringStream StreamCopyType; +}; + +} // namespace rapdijson +#endif + +TEST(Reader, CustomStringStream) { + const char* json = "{ \"hello\" : \"world\", \"t\" : true , \"f\" : false, \"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3] } "; + CustomStringStream > s(json); + ParseObjectHandler h; + Reader reader; + reader.ParseObject<0>(s, h); + EXPECT_EQ(20u, h.step_); +}