implemented pretty printing (issue #13)

- to_string() method is now called dump()
- syntax borrowed from Python’s json.dumps()
This commit is contained in:
Niels 2015-01-06 21:44:07 +01:00
parent 08456b8ff0
commit bd9f49efb9
4 changed files with 126 additions and 28 deletions

View file

@ -112,13 +112,13 @@ You can create an object (deserialization) by appending `_json` to a string lite
```cpp
// create object from string literal
json j = "{ \"pi\": 3.141, \"happy\": true }"_json;
json j = "{ \"happy\": true, \"pi\": 3.141 }"_json;
// or even nicer (thanks http://isocpp.org/blog/2015/01/json-for-modern-cpp)
auto j2 = R"(
{
"pi": 3.141,
"happy": true
"happy": true,
"pi": 3.141
})"_json;
```
@ -126,7 +126,14 @@ You can also get a string representation (serialize):
```cpp
// explicit conversion to string
std::string s = j.to_string();
std::string s = j.dump(); // {\"happy\": true, \"pi\": 3.141}
// serialization with pretty printing
std::cout << j.dump(4) << std::endl;
// {
// "happy": true,
// "pi": 3.141
// }
```
The value of s could be `{"pi": 3.141, "happy": true}`, but the order of the entries in the object is not fixed.

View file

@ -472,8 +472,22 @@ json::operator object_t() const
return get<object_t>();
}
const std::string json::to_string() const noexcept
/*!
Internal implementation of the serialization function.
\param prettyPrint whether the output shall be pretty-printed
\param indentStep the indent level
\param currentIndent the current indent level (only used internally)
*/
const std::string json::dump(const bool prettyPrint,
const unsigned int indentStep, unsigned int currentIndent) const noexcept
{
// helper function to return whitespace as indentation
const auto indent = [prettyPrint, &currentIndent]()
{
return prettyPrint ? std::string(currentIndent, ' ') : std::string();
};
switch (type_)
{
case (value_type::string):
@ -498,34 +512,73 @@ const std::string json::to_string() const noexcept
case (value_type::array):
{
std::string result;
if (value_.array->empty())
{
return "[]";
}
std::string result = "[";
// increase indentation
if (prettyPrint)
{
currentIndent += indentStep;
result += "\n";
}
for (array_t::const_iterator i = value_.array->begin(); i != value_.array->end(); ++i)
{
if (i != value_.array->begin())
{
result += ", ";
result += prettyPrint ? ",\n" : ", ";
}
result += i->to_string();
result += indent() + i->dump(prettyPrint, indentStep, currentIndent);
}
return "[" + result + "]";
// decrease indentation
if (prettyPrint)
{
currentIndent -= indentStep;
result += "\n";
}
return result + indent() + "]";
}
case (value_type::object):
{
std::string result;
if (value_.object->empty())
{
return "{}";
}
std::string result = "{";
// increase indentation
if (prettyPrint)
{
currentIndent += indentStep;
result += "\n";
}
for (object_t::const_iterator i = value_.object->begin(); i != value_.object->end(); ++i)
{
if (i != value_.object->begin())
{
result += ", ";
result += prettyPrint ? ",\n" : ", ";
}
result += "\"" + i->first + "\": " + i->second.to_string();
result += indent() + "\"" + i->first + "\": " + i->second.dump(prettyPrint, indentStep,
currentIndent);
}
return "{" + result + "}";
// decrease indentation
if (prettyPrint)
{
currentIndent -= indentStep;
result += "\n";
}
return result + indent() + "}";
}
// actually only value_type::null - but making the compiler happy
@ -536,6 +589,29 @@ const std::string json::to_string() const noexcept
}
}
/*!
Serialization function for JSON objects. The function tries to mimick Python's
\p json.dumps() function, and currently supports its \p indent parameter.
\param indent if indent is nonnegative, then array elements and object members
will be pretty-printed with that indent level. An indent level
of 0 will only insert newlines. -1 (the default) selects the
most compact representation
\see https://docs.python.org/2/library/json.html#json.dump
*/
const std::string json::dump(int indent) const noexcept
{
if (indent >= 0)
{
return dump(true, static_cast<unsigned int>(indent));
}
else
{
return dump(false, 0);
}
}
///////////////////////////////////////////
// ADDING ELEMENTS TO OBJECTS AND ARRAYS //

View file

@ -162,6 +162,9 @@ class json
/// return the type as string
const std::string type_name() const noexcept;
/// dump the object (with pretty printer)
const std::string dump(const bool, const unsigned int, unsigned int = 0) const noexcept;
public:
/// explicit value conversion
template<typename T>
@ -180,34 +183,34 @@ class json
/// implicit conversion to JSON map (only for objects)
operator object_t() const;
/// write to stream
/// serialize to stream
friend std::ostream& operator<<(std::ostream& o, const json& j)
{
o << j.to_string();
o << j.dump();
return o;
}
/// write to stream
/// serialize to stream
friend std::ostream& operator>>(const json& j, std::ostream& o)
{
o << j.to_string();
o << j.dump();
return o;
}
/// read from stream
/// deserialize from stream
friend std::istream& operator>>(std::istream& i, json& j)
{
j = parser(i).parse();
return i;
}
/// read from stream
/// deserialize from stream
friend std::istream& operator<<(json& j, std::istream& i)
{
j = parser(i).parse();
return i;
}
/// explicit conversion to string representation (C++ style)
const std::string to_string() const noexcept;
/// explicit serialization
const std::string dump(int = -1) const noexcept;
/// add an object/array to an array
json& operator+=(const json&);

View file

@ -17,7 +17,7 @@ TEST_CASE("array")
const json j_const (j);
// string representation of default value
CHECK(j.to_string() == "[]");
CHECK(j.dump() == "[]");
// iterators
CHECK(j.begin() != j.end());
@ -305,7 +305,7 @@ TEST_CASE("object")
const json j_const = j;
// string representation of default value
CHECK(j.to_string() == "{}");
CHECK(j.dump() == "{}");
// iterators
CHECK(j.begin() != j.end());
@ -685,7 +685,7 @@ TEST_CASE("null")
CHECK(j.type() == json::value_type::null);
// string representation of default value
CHECK(j.to_string() == "null");
CHECK(j.dump() == "null");
// iterators
CHECK(j.begin() != j.end());
@ -755,7 +755,7 @@ TEST_CASE("string")
CHECK(j.cbegin() != j.cend());
// string representation of default value
CHECK(j.to_string() == "\"\"");
CHECK(j.dump() == "\"\"");
// container members
CHECK(j.size() == 1);
@ -837,7 +837,7 @@ TEST_CASE("boolean")
CHECK(j.cbegin() != j.cend());
// string representation of default value
CHECK(j.to_string() == "false");
CHECK(j.dump() == "false");
// container members
CHECK(j.size() == 1);
@ -916,7 +916,7 @@ TEST_CASE("number (int)")
CHECK(j.cbegin() != j.cend());
// string representation of default value
CHECK(j.to_string() == "0");
CHECK(j.dump() == "0");
// container members
CHECK(j.size() == 1);
@ -1002,7 +1002,7 @@ TEST_CASE("number (float)")
CHECK(j.cbegin() != j.cend());
// string representation of default value
CHECK(j.to_string() == "0.000000");
CHECK(j.dump() == "0.000000");
// container members
CHECK(j.size() == 1);
@ -1786,6 +1786,18 @@ TEST_CASE("Parser")
CHECK(j22 == j23);
}
SECTION("serialization")
{
auto j23 = "{ \"a\": null, \"b\": true, \"c\": [1,2,3], \"d\": {\"a\": 0} }"_json;
CHECK(j23.dump() == "{\"a\": null, \"b\": true, \"c\": [1, 2, 3], \"d\": {\"a\": 0}}");
CHECK(j23.dump(-1) == "{\"a\": null, \"b\": true, \"c\": [1, 2, 3], \"d\": {\"a\": 0}}");
CHECK(j23.dump(0) ==
"{\n\"a\": null,\n\"b\": true,\n\"c\": [\n1,\n2,\n3\n],\n\"d\": {\n\"a\": 0\n}\n}");
CHECK(j23.dump(4) ==
"{\n \"a\": null,\n \"b\": true,\n \"c\": [\n 1,\n 2,\n 3\n ],\n \"d\": {\n \"a\": 0\n }\n}");
}
SECTION("Errors")
{
CHECK_THROWS_AS(json::parse(""), std::invalid_argument);