diff --git a/Data/Data_VS90.vcproj b/Data/Data_VS90.vcproj index 4db2814e5..bdd6f6e7b 100644 --- a/Data/Data_VS90.vcproj +++ b/Data/Data_VS90.vcproj @@ -583,6 +583,10 @@ RelativePath=".\include\Poco\Data\Extraction.h" > + + @@ -723,6 +727,10 @@ RelativePath=".\src\DynamicLOB.cpp" > + + diff --git a/Data/Data_vs160.vcxproj b/Data/Data_vs160.vcxproj index b1e83bf28..829557603 100644 --- a/Data/Data_vs160.vcxproj +++ b/Data/Data_vs160.vcxproj @@ -569,6 +569,7 @@ + @@ -635,6 +636,9 @@ true + + true + true diff --git a/Data/Data_vs160.vcxproj.filters b/Data/Data_vs160.vcxproj.filters index b9ddffbeb..eed50b05b 100644 --- a/Data/Data_vs160.vcxproj.filters +++ b/Data/Data_vs160.vcxproj.filters @@ -2,31 +2,31 @@ - {d0ab5265-2864-49b7-bd0f-8ac8ec3310ab} + {ca802690-e052-4003-ae74-b2cd7e2c95f9} - {816b5750-54e8-4eea-9b7e-932128a4910b} + {c9a3889d-4e2d-43ac-887e-3b22b1cf4ba9} - {f2c0b14b-1f5c-44a6-9c96-d8a1dddba2f9} + {b6b0997e-c4d7-4526-88c6-614fba4407cf} - {b1da33d1-7edf-4c90-a82b-65bc3c051030} + {ca986e9c-3287-419e-8f6a-7472a96c71b9} - {b8f868fc-fc3a-451f-b8f2-b533c7b119b8} + {33a16a37-8706-47fb-ac35-f3e913ff6a03} - {8fa22f8e-34b1-415c-81c1-e8dad60db327} + {934c7f71-50fb-452a-94a6-c123cdaa043e} - {7bcc1fd5-c9f1-4063-9948-efa1cdb00e46} + {e6946b3f-5400-4275-a315-25a8db846def} - {d01e6c3f-45e1-4b0e-9fa5-690bfd21fe6d} + {6fd48faa-50da-4192-bf66-27068d6b2156} - {f242ef0d-23d3-4b7b-a8ac-dbd1826bf531} + {71eaafa2-e2fe-4cb8-80e2-82ea694abeec} @@ -93,6 +93,9 @@ DataCore\Header Files + + DataCore\Header Files + DataCore\Header Files @@ -215,6 +218,9 @@ DataCore\Source Files + + DataCore\Source Files + DataCore\Source Files diff --git a/Data/Makefile b/Data/Makefile index b863b9671..ea354f6c2 100644 --- a/Data/Makefile +++ b/Data/Makefile @@ -8,8 +8,8 @@ include $(POCO_BASE)/build/rules/global objects = AbstractBinder AbstractBinding AbstractExtraction AbstractExtractor \ AbstractPreparation AbstractPreparator ArchiveStrategy Transaction \ - Bulk Connector DataException Date DynamicLOB Limit MetaColumn \ - PooledSessionHolder PooledSessionImpl Position \ + Bulk Connector DataException Date DynamicLOB JSONRowFormatter \ + Limit MetaColumn PooledSessionHolder PooledSessionImpl Position \ Range RecordSet Row RowFilter RowFormatter RowIterator \ SimpleRowFormatter Session SessionFactory SessionImpl \ SessionPool SessionPoolContainer SQLChannel \ diff --git a/Data/include/Poco/Data/JSONRowFormatter.h b/Data/include/Poco/Data/JSONRowFormatter.h new file mode 100644 index 000000000..8308e9ab4 --- /dev/null +++ b/Data/include/Poco/Data/JSONRowFormatter.h @@ -0,0 +1,159 @@ +// +// JSONRowFormatter.h +// +// Library: Data +// Package: DataCore +// Module: JSONRowFormatter +// +// Definition of the JSONRowFormatter class. +// +// Copyright (c) 2006, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#ifndef Data_JSONRowFormatter_INCLUDED +#define Data_JSONRowFormatter_INCLUDED + + +#include "Poco/Data/RowFormatter.h" + + +namespace Poco { +namespace Data { + + +class Data_API JSONRowFormatter: public Poco::Data::RowFormatter + /// Class for JSON formatting of data rows. + /// + /// Formatter can be configured to operate in four modes (and + /// certain combinations thereof) : + /// + /// - small (condensed mode, only array of values) + /// + /// Example: + /// { + /// [["Simpson", "Bart", "Springfield", 12], + /// ["Simpson", "Lisa", "Springfield", 10]] + /// } + /// + /// - row count (total row count provided) + /// + /// Example: + /// { + /// "count":2, + /// [["Simpson", "Bart", "Springfield", 12], + /// ["Simpson", "Lisa", "Springfield", 10]] + /// } + /// + /// - column names (column names provided as a string array) + /// + /// Example: + /// { + /// "names":["LastName", "FirstName", "Address", "Age"], + /// [["Simpson", "Bart", "Springfield", 12], + /// ["Simpson", "Lisa", "Springfield", 10]] + /// } + /// + /// - full (total row count, column names provided in every row of data) + /// + /// Example: + /// { + /// "count":2, + /// [ + /// {"LastName": "Simpson", "FirstName": "Bart", "Address": "Springfield", "Age": 12}, + /// {"LastName": "Simpson", "FirstName": "Lisa", "Address": "Springfield", "Age": 10} + /// ] + /// } + /// + /// Total row count will be specified by the Poco::SQLRecordSet. Note, however, that this is + /// not possible to do accurately in case of result set paging. For those cases, there is + /// setTotalRowCount() member function, which allows to explicitly set the total row count. + /// If the total row count is preset on the formatter, the Data framework shall not interfere. +{ +public: + static const int JSON_FMT_MODE_SMALL = 1; + static const int JSON_FMT_MODE_ROW_COUNT = 2; + static const int JSON_FMT_MODE_COLUMN_NAMES = 4; + static const int JSON_FMT_MODE_FULL = 8; + + JSONRowFormatter(int mode = (JSON_FMT_MODE_COLUMN_NAMES | JSON_FMT_MODE_SMALL)); + /// Creates a new JSONRowFormatter. + + ~JSONRowFormatter(); + /// Destroys the JSONRowFormatter. + + std::string& formatNames(const NameVecPtr pNames, std::string& formattedNames); + /// Formats names. + + std::string& formatValues(const ValueVec& vals, std::string& formattedValues); + // Formats values. + + void setJSONMode(int mode); + /// Sets the mode. Valid mode values are: + /// JSON_FMT_MODE_SMALL + /// JSON_FMT_MODE_ROW_COUNT + /// JSON_FMT_MODE_COLUMN_NAMES + /// JSON_FMT_MODE_FULL + + bool printRowCount() const; + /// Returns true if row count printing is enabled, + /// false otherwise. + + bool printColumnNames() const; + /// Returns true if column names printing is enabled, + /// false otherwise. + + bool isSmall() const; + /// Returns true if compact mode formatting is enabled, + /// false otherwise. + + bool isFull() const; + /// Returns true if full mode formatting is enabled, + /// false otherwise. + + +private: + void adjustPrefix() const; + + NameVecPtr _pNames; + int _mode; + bool _firstTime; +}; + + +// +// inlines +// + + +inline bool JSONRowFormatter::printRowCount() const +{ + return (_mode & JSON_FMT_MODE_ROW_COUNT) != 0; +} + + +inline bool JSONRowFormatter::printColumnNames() const +{ + return (_mode & JSON_FMT_MODE_COLUMN_NAMES) != 0; +} + + +inline bool JSONRowFormatter::isSmall() const +{ + return (_mode & JSON_FMT_MODE_SMALL) != 0; +} + + +inline bool JSONRowFormatter::isFull() const +{ + return (_mode & JSON_FMT_MODE_FULL) != 0; +} + + +} } // namespace Poco::Data + + +#endif // Data_JSONRowFormatter_INCLUDED diff --git a/Data/include/Poco/Data/RowFormatter.h b/Data/include/Poco/Data/RowFormatter.h index a56fe6646..7c735a18d 100644 --- a/Data/include/Poco/Data/RowFormatter.h +++ b/Data/include/Poco/Data/RowFormatter.h @@ -139,7 +139,12 @@ public: protected: - void setPrefix(const std::string& prefix); + virtual void adjustPrefix() const; + /// Adjusts the prefix, if needed + /// (eg. to contain the total row count); + /// default no-op. + + void setPrefix(const std::string& prefix) const; /// Sets the prefix for the formatter. void setPostfix(const std::string& postfix); @@ -175,7 +180,7 @@ inline void RowFormatter::setTotalRowCount(int count) } -inline void RowFormatter::setPrefix(const std::string& prefix) +inline void RowFormatter::setPrefix(const std::string& prefix) const { _prefix = prefix; } @@ -189,6 +194,7 @@ inline void RowFormatter::setPostfix(const std::string& postfix) inline const std::string& RowFormatter::prefix() const { + adjustPrefix(); return _prefix; } diff --git a/Data/samples/RowFormatter/src/RowFormatter.cpp b/Data/samples/RowFormatter/src/RowFormatter.cpp index 122dbd361..4c928f940 100644 --- a/Data/samples/RowFormatter/src/RowFormatter.cpp +++ b/Data/samples/RowFormatter/src/RowFormatter.cpp @@ -17,6 +17,7 @@ #include "Poco/Data/Statement.h" #include "Poco/Data/RecordSet.h" #include "Poco/Data/RowFormatter.h" +#include "Poco/Data/JSONRowFormatter.h" #include "Poco/Data/SQLite/Connector.h" #include @@ -27,6 +28,7 @@ using Poco::Data::Session; using Poco::Data::Statement; using Poco::Data::RecordSet; using Poco::Data::RowFormatter; +using Poco::Data::JSONRowFormatter; class HTMLTableFormatter : public RowFormatter @@ -117,5 +119,11 @@ int main(int argc, char** argv) std::cout << std::endl << "Simple formatting:" << std::endl << std::endl; std::cout << RecordSet(session, "SELECT * FROM Simpsons"); + // JSON formatting example (uses the JSONRowFormatter provided by framework) + std::cout << std::endl << "JSON formatting:" << std::endl << std::endl; + JSONRowFormatter jsonRowFormatter; + jsonRowFormatter.setJSONMode((RowFormatter::Mode)(JSONRowFormatter::JSON_FMT_MODE_ROW_COUNT | JSONRowFormatter::JSON_FMT_MODE_COLUMN_NAMES)); + std::cout << RecordSet(session, "SELECT * FROM Simpsons", jsonRowFormatter); + return 0; } diff --git a/Data/src/JSONRowFormatter.cpp b/Data/src/JSONRowFormatter.cpp new file mode 100644 index 000000000..3d5dc4fe2 --- /dev/null +++ b/Data/src/JSONRowFormatter.cpp @@ -0,0 +1,189 @@ +// +// JSONRowFormatter.cpp +// +// Library: Data +// Package: DataCore +// Module: JSONRowFormatter +// +// Copyright (c) 2006, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#include "Poco/Data/JSONRowFormatter.h" +#include "Poco/String.h" +#include "Poco/JSONString.h" +#include "Poco/Format.h" + + +using Poco::trimInPlace; +using Poco::format; +using Poco::toJSON; + + +namespace Poco { +namespace Data { + + +const int JSONRowFormatter::JSON_FMT_MODE_SMALL; +const int JSONRowFormatter::JSON_FMT_MODE_ROW_COUNT; +const int JSONRowFormatter::JSON_FMT_MODE_COLUMN_NAMES; +const int JSONRowFormatter::JSON_FMT_MODE_FULL; + + +JSONRowFormatter::JSONRowFormatter(int mode) : RowFormatter("{", "]}"), + _firstTime(true) +{ + if (mode == JSON_FMT_MODE_FULL) + { + mode |= JSON_FMT_MODE_ROW_COUNT; + mode |= JSON_FMT_MODE_COLUMN_NAMES; + } + + setJSONMode(mode); +} + + +JSONRowFormatter::~JSONRowFormatter() +{ +} + + +void JSONRowFormatter::adjustPrefix() const +{ + if (printRowCount()) + { + std::ostringstream ostr; + ostr << "{\"count\":" << getTotalRowCount() << ","; + if (_mode & JSON_FMT_MODE_FULL) + ostr << '['; + setPrefix(ostr.str()); + } +} + + +void JSONRowFormatter::setJSONMode(int mode) +{ + if (mode < JSON_FMT_MODE_SMALL || + mode > (JSON_FMT_MODE_SMALL | JSON_FMT_MODE_ROW_COUNT | JSON_FMT_MODE_COLUMN_NAMES | JSON_FMT_MODE_FULL)) + { + throw Poco::InvalidArgumentException( + Poco::format("JSONRowFormatter mode must be between " + "%d (JSON_FMT_MODE_SMALL) and %d (JSON_FMT_MODE_FULL)", + JSON_FMT_MODE_SMALL, + JSON_FMT_MODE_FULL)); + } + + _mode = mode; + if (!(_mode & JSON_FMT_MODE_SMALL) && !(_mode & JSON_FMT_MODE_FULL)) + _mode |= JSON_FMT_MODE_SMALL; + else if (_mode & JSON_FMT_MODE_FULL) + { + _mode |= JSON_FMT_MODE_ROW_COUNT; + } +} + + +std::string& JSONRowFormatter::formatValues(const ValueVec& vals, std::string& formattedValues) +{ + std::ostringstream str; + if (!_firstTime) str << ','; + if (isSmall()) + { + if (_firstTime) + { + if (printColumnNames()) + str << ",\"values\":"; + + str << '['; + } + + str << '['; + ValueVec::const_iterator it = vals.begin(); + ValueVec::const_iterator end = vals.end(); + for (; it != end;) + { + if (!it->isEmpty()) + { + if (it->isString() || it->isDate() || it->isTime()) + { + std::string val = it->convert(); + trimInPlace(val); + str << toJSON(val); + } + else + str << it->convert(); + } + else + str << "null"; + + if (++it == end) break; + + str << ','; + } + str << ']'; + } + else if (isFull()) + { + str << '{'; + ValueVec::const_iterator it = vals.begin(); + ValueVec::const_iterator end = vals.end(); + NameVec::iterator nIt = _pNames->begin(); + NameVec::iterator nEnd = _pNames->end(); + for (; it != end && nIt != nEnd; ++nIt) + { + if (!it->isEmpty()) + { + if (it->isString() || it->isDate() || it->isTime()) + { + std::string val = it->convert(); + trimInPlace(val); + str << '"' << *nIt << "\":" << toJSON(val); + } + else + str << '"' << *nIt << "\":" << it->convert(); + } + else + str << '"' << *nIt << "\":null"; + + if (++it != end) str << ','; + } + str << '}'; + } + + _firstTime = false; + return formattedValues = str.str(); +} + + +std::string& JSONRowFormatter::formatNames(const NameVecPtr pNames, std::string& formattedNames) +{ + //adjustPrefix(); + if (isFull()) + { + // names are used in formatValues + if (pNames && !_pNames) _pNames = pNames; + return formattedNames = ""; + } + else if (printColumnNames()) + { + std::ostringstream ostr; + ostr << "\"names\":["; + for (NameVec::const_iterator it = pNames->begin(), + end = pNames->end();;) + { + ostr << '"' << *it << '"'; + if (++it == end) break; + ostr << ','; + } + ostr << "]"; + return formattedNames = ostr.str(); + } + + return formattedNames = ""; +} + + +} }// namespace Poco::Data diff --git a/Data/src/RowFormatter.cpp b/Data/src/RowFormatter.cpp index 071c80f6f..3173fd84e 100644 --- a/Data/src/RowFormatter.cpp +++ b/Data/src/RowFormatter.cpp @@ -77,4 +77,10 @@ void RowFormatter::reset() } +void RowFormatter::adjustPrefix() const +{ + return; +} + + } } // namespace Poco::Data diff --git a/Data/testsuite/src/DataTest.cpp b/Data/testsuite/src/DataTest.cpp index afda8fb78..7c9159655 100644 --- a/Data/testsuite/src/DataTest.cpp +++ b/Data/testsuite/src/DataTest.cpp @@ -21,6 +21,7 @@ #include "Poco/Data/Date.h" #include "Poco/Data/Time.h" #include "Poco/Data/SimpleRowFormatter.h" +#include "Poco/Data/JSONRowFormatter.h" #include "Poco/Data/DataException.h" #include "Connector.h" #include "Poco/BinaryReader.h" @@ -65,7 +66,9 @@ using Poco::Data::CLOBOutputStream; using Poco::Data::MetaColumn; using Poco::Data::Column; using Poco::Data::Row; +using Poco::Data::RowFormatter; using Poco::Data::SimpleRowFormatter; +using Poco::Data::JSONRowFormatter; using Poco::Data::Date; using Poco::Data::Time; using Poco::Data::AbstractExtractor; @@ -1156,7 +1159,7 @@ void DataTest::testRowStrictWeak(const Row& row1, const Row& row2, const Row& ro } -void DataTest::testRowFormat() +void DataTest::testSimpleRowFormatter() { Row row1; row1.append("field0", 0); @@ -1200,6 +1203,38 @@ void DataTest::testRowFormat() } +void DataTest::testJSONRowFormatter() +{ + Row row1; + row1.append("field0", 0); + row1.append("field1", "1"); + row1.append("field2", DateTime(2007, 3, 13, 8, 12, 15)); + row1.append("field3", Var()); + row1.append("field4", 4); + row1.setFormatter(new JSONRowFormatter); + + assertTrue(row1.getFormatter().prefix() == "{"); + assertTrue(row1.getFormatter().postfix() == "]}"); + assertTrue(row1.getFormatter().getMode() == RowFormatter::FORMAT_PROGRESSIVE); + assertTrue(row1.namesToString() == "\"names\":[\"field0\",\"field1\",\"field2\",\"field3\",\"field4\"]"); + assertTrue(row1.valuesToString() == ",\"values\":[[0,\"1\",\"2007-03-13T08:12:15Z\",null,4]"); + + row1.setFormatter(new JSONRowFormatter(JSONRowFormatter::JSON_FMT_MODE_SMALL)); + assertTrue(row1.getFormatter().getMode() == RowFormatter::FORMAT_PROGRESSIVE); + assertTrue(row1.namesToString() == ""); + assertTrue(row1.valuesToString() == "[[0,\"1\",\"2007-03-13T08:12:15Z\",null,4]"); + assertTrue(row1.valuesToString() == ",[0,\"1\",\"2007-03-13T08:12:15Z\",null,4]"); + + row1.setFormatter(new JSONRowFormatter(JSONRowFormatter::JSON_FMT_MODE_FULL)); + assertTrue(row1.getFormatter().prefix() == "{\"count\":0,["); + assertTrue(row1.getFormatter().postfix() == "]}"); + assertTrue(row1.getFormatter().getMode() == RowFormatter::FORMAT_PROGRESSIVE); + assertTrue(row1.namesToString() == ""); + assertTrue(row1.valuesToString() == "{\"field0\":0,\"field1\":\"1\",\"field2\":\"2007-03-13T08:12:15Z\",\"field3\":null,\"field4\":4}"); + assertTrue(row1.valuesToString() == ",{\"field0\":0,\"field1\":\"1\",\"field2\":\"2007-03-13T08:12:15Z\",\"field3\":null,\"field4\":4}"); +} + + void DataTest::testDateAndTime() { DateTime dt; @@ -1415,7 +1450,8 @@ CppUnit::Test* DataTest::suite() CppUnit_addTest(pSuite, DataTest, testColumnList); CppUnit_addTest(pSuite, DataTest, testRow); CppUnit_addTest(pSuite, DataTest, testRowSort); - CppUnit_addTest(pSuite, DataTest, testRowFormat); + CppUnit_addTest(pSuite, DataTest, testSimpleRowFormatter); + CppUnit_addTest(pSuite, DataTest, testJSONRowFormatter); CppUnit_addTest(pSuite, DataTest, testDateAndTime); CppUnit_addTest(pSuite, DataTest, testExternalBindingAndExtraction); CppUnit_addTest(pSuite, DataTest, testTranscode); diff --git a/Data/testsuite/src/DataTest.h b/Data/testsuite/src/DataTest.h index 6b5015616..ae3241166 100644 --- a/Data/testsuite/src/DataTest.h +++ b/Data/testsuite/src/DataTest.h @@ -40,7 +40,8 @@ public: void testColumnList(); void testRow(); void testRowSort(); - void testRowFormat(); + void testSimpleRowFormatter();; + void testJSONRowFormatter(); void testDateAndTime(); void testExternalBindingAndExtraction(); void testTranscode();