diff --git a/README.md b/README.md index 7f42b530..203f1134 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/src/json.cc b/src/json.cc index fdc81479..04dca7cf 100644 --- a/src/json.cc +++ b/src/json.cc @@ -472,8 +472,22 @@ json::operator object_t() const return get(); } -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, ¤tIndent]() + { + 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(indent)); + } + else + { + return dump(false, 0); + } +} + /////////////////////////////////////////// // ADDING ELEMENTS TO OBJECTS AND ARRAYS // diff --git a/src/json.h b/src/json.h index cc7de071..9747d791 100644 --- a/src/json.h +++ b/src/json.h @@ -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 @@ -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&); diff --git a/test/json_unit.cc b/test/json_unit.cc index 32132c82..f820ffa8 100644 --- a/test/json_unit.cc +++ b/test/json_unit.cc @@ -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);