feat(Data): Add JSONRowFormatter #3602

This commit is contained in:
Alex Fabijanic 2022-05-22 12:41:34 -07:00
parent 78558f868d
commit ebeef47a8c
11 changed files with 439 additions and 16 deletions

View File

@ -583,6 +583,10 @@
RelativePath=".\include\Poco\Data\Extraction.h"
>
</File>
<File
RelativePath=".\include\Poco\Data\JSONRowFormatter.h"
>
</File>
<File
RelativePath=".\include\Poco\Data\Limit.h"
>
@ -723,6 +727,10 @@
RelativePath=".\src\DynamicLOB.cpp"
>
</File>
<File
RelativePath=".\src\JSONRowFormatter.cpp"
>
</File>
<File
RelativePath=".\src\Limit.cpp"
>

View File

@ -569,6 +569,7 @@
<ClInclude Include="include\Poco\Data\DynamicDateTime.h"/>
<ClInclude Include="include\Poco\Data\DynamicLOB.h"/>
<ClInclude Include="include\Poco\Data\Extraction.h"/>
<ClInclude Include="include\Poco\Data\JSONRowFormatter.h"/>
<ClInclude Include="include\Poco\Data\Limit.h"/>
<ClInclude Include="include\Poco\Data\LOB.h"/>
<ClInclude Include="include\Poco\Data\LOBStream.h"/>
@ -635,6 +636,9 @@
<ClCompile Include="src\DynamicLOB.cpp">
<MultiProcessorCompilation>true</MultiProcessorCompilation>
</ClCompile>
<ClCompile Include="src\JSONRowFormatter.cpp">
<MultiProcessorCompilation>true</MultiProcessorCompilation>
</ClCompile>
<ClCompile Include="src\Limit.cpp">
<MultiProcessorCompilation>true</MultiProcessorCompilation>
</ClCompile>

View File

@ -2,31 +2,31 @@
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="DataCore">
<UniqueIdentifier>{d0ab5265-2864-49b7-bd0f-8ac8ec3310ab}</UniqueIdentifier>
<UniqueIdentifier>{ca802690-e052-4003-ae74-b2cd7e2c95f9}</UniqueIdentifier>
</Filter>
<Filter Include="DataCore\Header Files">
<UniqueIdentifier>{816b5750-54e8-4eea-9b7e-932128a4910b}</UniqueIdentifier>
<UniqueIdentifier>{c9a3889d-4e2d-43ac-887e-3b22b1cf4ba9}</UniqueIdentifier>
</Filter>
<Filter Include="DataCore\Source Files">
<UniqueIdentifier>{f2c0b14b-1f5c-44a6-9c96-d8a1dddba2f9}</UniqueIdentifier>
<UniqueIdentifier>{b6b0997e-c4d7-4526-88c6-614fba4407cf}</UniqueIdentifier>
</Filter>
<Filter Include="SessionPooling">
<UniqueIdentifier>{b1da33d1-7edf-4c90-a82b-65bc3c051030}</UniqueIdentifier>
<UniqueIdentifier>{ca986e9c-3287-419e-8f6a-7472a96c71b9}</UniqueIdentifier>
</Filter>
<Filter Include="SessionPooling\Header Files">
<UniqueIdentifier>{b8f868fc-fc3a-451f-b8f2-b533c7b119b8}</UniqueIdentifier>
<UniqueIdentifier>{33a16a37-8706-47fb-ac35-f3e913ff6a03}</UniqueIdentifier>
</Filter>
<Filter Include="SessionPooling\Source Files">
<UniqueIdentifier>{8fa22f8e-34b1-415c-81c1-e8dad60db327}</UniqueIdentifier>
<UniqueIdentifier>{934c7f71-50fb-452a-94a6-c123cdaa043e}</UniqueIdentifier>
</Filter>
<Filter Include="Logging">
<UniqueIdentifier>{7bcc1fd5-c9f1-4063-9948-efa1cdb00e46}</UniqueIdentifier>
<UniqueIdentifier>{e6946b3f-5400-4275-a315-25a8db846def}</UniqueIdentifier>
</Filter>
<Filter Include="Logging\Header Files">
<UniqueIdentifier>{d01e6c3f-45e1-4b0e-9fa5-690bfd21fe6d}</UniqueIdentifier>
<UniqueIdentifier>{6fd48faa-50da-4192-bf66-27068d6b2156}</UniqueIdentifier>
</Filter>
<Filter Include="Logging\Source Files">
<UniqueIdentifier>{f242ef0d-23d3-4b7b-a8ac-dbd1826bf531}</UniqueIdentifier>
<UniqueIdentifier>{71eaafa2-e2fe-4cb8-80e2-82ea694abeec}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
@ -93,6 +93,9 @@
<ClInclude Include="include\Poco\Data\Extraction.h">
<Filter>DataCore\Header Files</Filter>
</ClInclude>
<ClInclude Include="include\Poco\Data\JSONRowFormatter.h">
<Filter>DataCore\Header Files</Filter>
</ClInclude>
<ClInclude Include="include\Poco\Data\Limit.h">
<Filter>DataCore\Header Files</Filter>
</ClInclude>
@ -215,6 +218,9 @@
<ClCompile Include="src\DynamicLOB.cpp">
<Filter>DataCore\Source Files</Filter>
</ClCompile>
<ClCompile Include="src\JSONRowFormatter.cpp">
<Filter>DataCore\Source Files</Filter>
</ClCompile>
<ClCompile Include="src\Limit.cpp">
<Filter>DataCore\Source Files</Filter>
</ClCompile>

View File

@ -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 \

View File

@ -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

View File

@ -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;
}

View File

@ -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 <iostream>
@ -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;
}

View File

@ -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<std::string>();
trimInPlace(val);
str << toJSON(val);
}
else
str << it->convert<std::string>();
}
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<std::string>();
trimInPlace(val);
str << '"' << *nIt << "\":" << toJSON(val);
}
else
str << '"' << *nIt << "\":" << it->convert<std::string>();
}
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

View File

@ -77,4 +77,10 @@ void RowFormatter::reset()
}
void RowFormatter::adjustPrefix() const
{
return;
}
} } // namespace Poco::Data

View File

@ -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);

View File

@ -40,7 +40,8 @@ public:
void testColumnList();
void testRow();
void testRowSort();
void testRowFormat();
void testSimpleRowFormatter();;
void testJSONRowFormatter();
void testDateAndTime();
void testExternalBindingAndExtraction();
void testTranscode();