diff --git a/include/json/reader.h b/include/json/reader.h index bd2204b..1111a7b 100644 --- a/include/json/reader.h +++ b/include/json/reader.h @@ -14,6 +14,7 @@ #include #include #include +#include // Disable warning C4251: : needs to have dll-interface to // be used by... @@ -78,7 +79,7 @@ public: document to read. * \param endDoc Pointer on the end of the UTF-8 encoded string of the document to read. - \ Must be >= beginDoc. + * Must be >= beginDoc. * \param root [out] Contains the root value of the document if it was * successfully parsed. * \param collectComments \c true to collect comment and allow writing them @@ -238,8 +239,69 @@ private: std::string commentsBefore_; Features features_; bool collectComments_; +}; // Reader + +/** Interface for reading JSON from a char array. + */ +class JSON_API CharReader { +public: + virtual ~CharReader() {} + /** \brief Read a Value from a JSON + document. + * The document must be a UTF-8 encoded string containing the document to read. + * + * \param beginDoc Pointer on the beginning of the UTF-8 encoded string of the + document to read. + * \param endDoc Pointer on the end of the UTF-8 encoded string of the + document to read. + * Must be >= beginDoc. + * \param root [out] Contains the root value of the document if it was + * successfully parsed. + * \param errs [out] Formatted error messages (if not NULL) + * a user friendly string that lists errors in the parsed + * document. + * \return \c true if the document was successfully parsed, \c false if an + error occurred. + */ + virtual bool parse( + char const* beginDoc, char const* endDoc, + Value* root, std::string* errs) = 0; + + class Factory { + public: + /// \brief Allocate a CharReader via operator new(). + virtual CharReader* newCharReader() const = 0; + }; // Factory +}; // CharReader + +class CharReaderBuilder : public CharReader::Factory { + bool collectComments_; + Features features_; +public: + CharReaderBuilder(); + + CharReaderBuilder& withCollectComments(bool v) { + collectComments_ = v; + return *this; + } + + CharReaderBuilder& withFeatures(Features const& v) { + features_ = v; + return *this; + } + + virtual CharReader* newCharReader() const; }; +/** Consume entire stream and use its begin/end. + * Someday we might have a real StreamReader, but for now this + * is convenient. + */ +bool parseFromStream( + CharReader::Factory const&, + std::istream&, + Value* root, std::string* errs); + /** \brief Read from 'sin' into 'root'. Always keep comments from the input JSON. diff --git a/src/lib_json/json_reader.cpp b/src/lib_json/json_reader.cpp index d2cff9a..119c3da 100644 --- a/src/lib_json/json_reader.cpp +++ b/src/lib_json/json_reader.cpp @@ -14,6 +14,8 @@ #include #include #include +#include +#include #if defined(_MSC_VER) && _MSC_VER < 1500 // VC++ 8.0 and below #define snprintf _snprintf @@ -26,6 +28,12 @@ namespace Json { +#if __cplusplus >= 201103L +typedef std::unique_ptr CharReaderPtr; +#else +typedef std::auto_ptr CharReaderPtr; +#endif + // Implementation of class Features // //////////////////////////////// @@ -882,13 +890,61 @@ bool Reader::good() const { return !errors_.size(); } +class OldReader : public CharReader { + bool const collectComments_; + Reader reader_; +public: + OldReader( + bool collectComments, + Features const& features) + : collectComments_(collectComments) + , reader_(features) + {} + virtual bool parse( + char const* beginDoc, char const* endDoc, + Value* root, std::string* errs) { + bool ok = reader_.parse(beginDoc, endDoc, *root, collectComments_); + if (errs) { + *errs = reader_.getFormattedErrorMessages(); + } + return ok; + } +}; + +CharReaderBuilder::CharReaderBuilder() + : collectComments_(true) + , features_(Features::all()) +{} +CharReader* CharReaderBuilder::newCharReader() const +{ + return new OldReader(collectComments_, features_); +} + +////////////////////////////////// +// global functions + +bool parseFromStream( + CharReader::Factory const& fact, std::istream& sin, + Value* root, std::string* errs) +{ + std::ostringstream ssin; + ssin << sin.rdbuf(); + std::string doc = ssin.str(); + char const* begin = doc.data(); + char const* end = begin + doc.size(); + // Note that we do not actually need a null-terminator. + CharReaderPtr const reader(fact.newCharReader()); + return reader->parse(begin, end, root, errs); +} + std::istream& operator>>(std::istream& sin, Value& root) { - Json::Reader reader; - bool ok = reader.parse(sin, root, true); + CharReaderBuilder b; + std::string errs; + bool ok = parseFromStream(b, sin, &root, &errs); if (!ok) { fprintf(stderr, "Error from reader: %s", - reader.getFormattedErrorMessages().c_str()); + errs.c_str()); JSON_FAIL_MESSAGE("reader error"); } diff --git a/src/test_lib_json/main.cpp b/src/test_lib_json/main.cpp index 8af3e19..e57fbc3 100644 --- a/src/test_lib_json/main.cpp +++ b/src/test_lib_json/main.cpp @@ -7,6 +7,7 @@ #include #include #include +#include // Make numeric limits more convenient to talk about. // Assumes int type in 32 bits. @@ -1617,6 +1618,90 @@ JSONTEST_FIXTURE(ReaderTest, parseWithDetailError) { JSONTEST_ASSERT(errors.at(0).message == "Bad escape sequence in string"); } +struct CharReaderTest : JsonTest::TestCase {}; + +JSONTEST_FIXTURE(CharReaderTest, parseWithNoErrors) { + Json::CharReaderBuilder b; + Json::CharReader* reader(b.newCharReader()); + std::string errs; + Json::Value root; + char const doc[] = "{ \"property\" : \"value\" }"; + bool ok = reader->parse( + doc, doc + std::strlen(doc), + &root, &errs); + JSONTEST_ASSERT(ok); + JSONTEST_ASSERT(errs.size() == 0); + delete reader; +} + +JSONTEST_FIXTURE(CharReaderTest, parseWithNoErrorsTestingOffsets) { + Json::CharReaderBuilder b; + Json::CharReader* reader(b.newCharReader()); + std::string errs; + Json::Value root; + char const doc[] = + "{ \"property\" : [\"value\", \"value2\"], \"obj\" : " + "{ \"nested\" : 123, \"bool\" : true}, \"null\" : " + "null, \"false\" : false }"; + bool ok = reader->parse( + doc, doc + std::strlen(doc), + &root, &errs); + JSONTEST_ASSERT(ok); + JSONTEST_ASSERT(errs.size() == 0); + delete reader; +} + +JSONTEST_FIXTURE(CharReaderTest, parseWithOneError) { + Json::CharReaderBuilder b; + Json::CharReader* reader(b.newCharReader()); + std::string errs; + Json::Value root; + char const doc[] = + "{ \"property\" :: \"value\" }"; + bool ok = reader->parse( + doc, doc + std::strlen(doc), + &root, &errs); + JSONTEST_ASSERT(!ok); + JSONTEST_ASSERT(errs == + "* Line 1, Column 15\n Syntax error: value, object or array " + "expected.\n"); + delete reader; +} + +JSONTEST_FIXTURE(CharReaderTest, parseChineseWithOneError) { + Json::CharReaderBuilder b; + Json::CharReader* reader(b.newCharReader()); + std::string errs; + Json::Value root; + char const doc[] = + "{ \"pr佐藤erty\" :: \"value\" }"; + bool ok = reader->parse( + doc, doc + std::strlen(doc), + &root, &errs); + JSONTEST_ASSERT(!ok); + JSONTEST_ASSERT(errs == + "* Line 1, Column 19\n Syntax error: value, object or array " + "expected.\n"); + delete reader; +} + +JSONTEST_FIXTURE(CharReaderTest, parseWithDetailError) { + Json::CharReaderBuilder b; + Json::CharReader* reader(b.newCharReader()); + std::string errs; + Json::Value root; + char const doc[] = + "{ \"property\" : \"v\\alue\" }"; + bool ok = reader->parse( + doc, doc + std::strlen(doc), + &root, &errs); + JSONTEST_ASSERT(!ok); + JSONTEST_ASSERT(errs == + "* Line 1, Column 16\n Bad escape sequence in string\nSee " + "Line 1, Column 20 for detail.\n"); + delete reader; +} + int main(int argc, const char* argv[]) { JsonTest::Runner runner; JSONTEST_REGISTER_FIXTURE(runner, ValueTest, checkNormalizeFloatingPointStr); @@ -1647,6 +1732,13 @@ int main(int argc, const char* argv[]) { JSONTEST_REGISTER_FIXTURE(runner, ReaderTest, parseChineseWithOneError); JSONTEST_REGISTER_FIXTURE(runner, ReaderTest, parseWithDetailError); + JSONTEST_REGISTER_FIXTURE(runner, CharReaderTest, parseWithNoErrors); + JSONTEST_REGISTER_FIXTURE( + runner, CharReaderTest, parseWithNoErrorsTestingOffsets); + JSONTEST_REGISTER_FIXTURE(runner, CharReaderTest, parseWithOneError); + JSONTEST_REGISTER_FIXTURE(runner, CharReaderTest, parseChineseWithOneError); + JSONTEST_REGISTER_FIXTURE(runner, CharReaderTest, parseWithDetailError); + JSONTEST_REGISTER_FIXTURE(runner, WriterTest, dropNullPlaceholders); return runner.runCommandLine(argc, argv);