diff --git a/src/stan/callbacks/json_writer.hpp b/src/stan/callbacks/json_writer.hpp index 54f54bf385..5b6299c792 100644 --- a/src/stan/callbacks/json_writer.hpp +++ b/src/stan/callbacks/json_writer.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -28,7 +29,7 @@ namespace callbacks { * output stream */ template > -class json_writer { +class json_writer final : public structured_writer { private: // Output stream std::unique_ptr output_{nullptr}; @@ -37,30 +38,17 @@ class json_writer { // Depth of records (used to determine whether or not to print comma // separator) int record_depth_ = 0; - // Whether or not the record's parent object needs a comma separator - bool record_needs_comma_ = false; - - /** - * Writes a comma separator for the record's parent object if needed. - */ - void write_record_comma_if_needed() { - if (record_depth_ > 0 && record_needs_comma_) { - *output_ << ",\n"; - record_needs_comma_ = false; - } else { - write_sep(); - } - } /** * Determines whether a record's internal object requires a comma separator */ void write_sep() { if (record_element_needs_comma_) { - *output_ << ", "; + *output_ << ","; } else { record_element_needs_comma_ = true; } + *output_ << "\n"; } /** @@ -109,7 +97,18 @@ class json_writer { * @param[in] key member name. */ void write_key(const std::string& key) { - *output_ << "\"" << process_string(key) << "\" : "; + *output_ << std::string(record_depth_ * 2, ' ') << "\"" + << process_string(key) << "\" : "; + } + + template + void write_int_like(const std::string& key, T value) { + if (output_ == nullptr) { + return; + } + write_sep(); + write_key(key); + *output_ << value; } /** @@ -144,78 +143,6 @@ class json_writer { *output_ << "]"; } - /** - * Writes a set of comma separated strings. - * Strings are cleaned to escape special characters. - * - * @param[in] v Values in a std::vector - */ - void write_vector(const std::vector& v) { - *output_ << "[ "; - if (v.size() > 0) { - auto last = v.end(); - --last; - for (auto it = v.begin(); it != last; ++it) { - *output_ << process_string(*it) << ", "; - } - } - *output_ << v.back() << " ]"; - } - - /** - * Writes a set of comma separated double values. - * - * @param[in] v Values in a std::vector - */ - void write_vector(const std::vector& v) { - *output_ << "[ "; - if (v.size() > 0) { - auto last = v.end(); - --last; - for (auto it = v.begin(); it != last; ++it) { - write_value(*it); - *output_ << ", "; - } - write_value(v.back()); - } - *output_ << " ]"; - } - - /** - * Writes a set of comma separated integer values. - * - * @param[in] v Values in a std::vector - */ - void write_vector(const std::vector& v) { - *output_ << "[ "; - if (v.size() > 0) { - auto last = v.end(); - --last; - for (auto it = v.begin(); it != last; ++it) { - *output_ << *it << ", "; - } - } - *output_ << v.back() << " ]"; - } - - /** - * Writes a set of comma separated complex values. - * - * @param[in] v Values in a std::vector - */ - void write_vector(const std::vector>& v) { - *output_ << "[ "; - if (v.size() > 0) { - size_t last = v.size() - 1; - for (size_t i = 0; i < last; ++i) { - write_complex_value(v[i]); - *output_ << ", "; - } - write_complex_value(v[last]); - } - *output_ << " ]"; - } - /** * Writes the set of comma separated values in an Eigen (row) vector. * @@ -258,15 +185,16 @@ class json_writer { json_writer(json_writer&& other) noexcept : output_(std::move(other.output_)) {} - ~json_writer() {} + virtual ~json_writer() {} /** * Writes "{", initial token of a JSON record. */ void begin_record() { - if (output_ == nullptr) + if (output_ == nullptr) { return; - write_record_comma_if_needed(); + } + write_sep(); *output_ << "{"; record_depth_++; record_element_needs_comma_ = false; @@ -277,10 +205,12 @@ class json_writer { * @param[in] key The name of the record. */ void begin_record(const std::string& key) { - if (output_ == nullptr) + if (output_ == nullptr) { return; - write_record_comma_if_needed(); - *output_ << "\"" << key << "\" : {"; + } + write_sep(); + write_key(key); + *output_ << "{"; record_depth_++; record_element_needs_comma_ = false; } @@ -288,12 +218,13 @@ class json_writer { * Writes "}", final token of a JSON record. */ void end_record() { - if (output_ == nullptr) + if (output_ == nullptr) { return; - *output_ << "}"; + } record_depth_--; + *output_ << "\n" << std::string(record_depth_ * 2, ' ') << "}"; if (record_depth_ > 0) { - record_needs_comma_ = true; + record_element_needs_comma_ = true; } else { *output_ << "\n"; } @@ -305,8 +236,9 @@ class json_writer { * @param key Name of the value pair */ void write(const std::string& key) { - if (output_ == nullptr) + if (output_ == nullptr) { return; + } write_sep(); write_key(key); *output_ << "null"; @@ -318,8 +250,9 @@ class json_writer { * @param value string to write. */ void write(const std::string& key, const std::string& value) { - if (output_ == nullptr) + if (output_ == nullptr) { return; + } std::string processsed_string = process_string(value); write_sep(); write_key(key); @@ -332,8 +265,9 @@ class json_writer { * @param value pointer to chars to write. */ void write(const std::string& key, const char* value) { - if (output_ == nullptr) + if (output_ == nullptr) { return; + } std::string processsed_string = process_string(value); write_sep(); write_key(key); @@ -346,8 +280,9 @@ class json_writer { * @param value bool to write. */ void write(const std::string& key, bool value) { - if (output_ == nullptr) + if (output_ == nullptr) { return; + } write_sep(); write_key(key); *output_ << (value ? "true" : "false"); @@ -358,13 +293,7 @@ class json_writer { * @param key Name of the value pair * @param value int to write. */ - void write(const std::string& key, int value) { - if (output_ == nullptr) - return; - write_sep(); - write_key(key); - *output_ << value; - } + void write(const std::string& key, int value) { write_int_like(key, value); } /** * Write a key-value pair where the value is an `std::size_t`. @@ -372,11 +301,27 @@ class json_writer { * @param value `std::size_t` to write. */ void write(const std::string& key, std::size_t value) { - if (output_ == nullptr) - return; - write_sep(); - write_key(key); - *output_ << value; + write_int_like(key, value); + } + + /** + * Write a key-value pair where the value is an `long long int`. + * @param key Name of the value pair + * @param value `long long int` to write. + */ + void write(const std::string& key, + long long int value // NOLINT(runtime/int) + ) { + write_int_like(key, value); + } + + /** + * Write a key-value pair where the value is an `unsigned int`. + * @param key Name of the value pair + * @param value `unsigned int` to write. + */ + void write(const std::string& key, unsigned int value) { + write_int_like(key, value); } /** @@ -385,8 +330,9 @@ class json_writer { * @param value double to write. */ void write(const std::string& key, double value) { - if (output_ == nullptr) + if (output_ == nullptr) { return; + } write_sep(); write_key(key); write_value(value); @@ -398,8 +344,9 @@ class json_writer { * @param value complex value to write. */ void write(const std::string& key, const std::complex& value) { - if (output_ == nullptr) + if (output_ == nullptr) { return; + } write_sep(); write_key(key); write_complex_value(value); @@ -410,13 +357,95 @@ class json_writer { * @param key Name of the value pair * @param values vector to write. */ - template - void write(const std::string& key, const std::vector& values) { - if (output_ == nullptr) + void write(const std::string& key, const std::vector& v) { + if (output_ == nullptr) { return; + } write_sep(); write_key(key); - write_vector(values); + + *output_ << "[ "; + if (v.size() > 0) { + auto last = v.end(); + --last; + for (auto it = v.begin(); it != last; ++it) { + *output_ << process_string(*it) << ", "; + } + } + *output_ << v.back() << " ]"; + } + + /** + * Write a key-value pair where the value is a vector to be made a list. + * @param key Name of the value pair + * @param values vector to write. + */ + void write(const std::string& key, const std::vector& v) { + if (output_ == nullptr) { + return; + } + write_sep(); + write_key(key); + + *output_ << "[ "; + if (v.size() > 0) { + auto last = v.end(); + --last; + for (auto it = v.begin(); it != last; ++it) { + write_value(*it); + *output_ << ", "; + } + write_value(v.back()); + } + *output_ << " ]"; + } + + /** + * Write a key-value pair where the value is a vector to be made a list. + * @param key Name of the value pair + * @param values vector to write. + */ + void write(const std::string& key, const std::vector& v) { + if (output_ == nullptr) { + return; + } + write_sep(); + write_key(key); + + *output_ << "[ "; + if (v.size() > 0) { + auto last = v.end(); + --last; + for (auto it = v.begin(); it != last; ++it) { + *output_ << *it << ", "; + } + } + *output_ << v.back() << " ]"; + } + + /** + * Write a key-value pair where the value is a vector to be made a list. + * @param key Name of the value pair + * @param values vector to write. + */ + void write(const std::string& key, + const std::vector>& v) { + if (output_ == nullptr) { + return; + } + write_sep(); + write_key(key); + + *output_ << "[ "; + if (v.size() > 0) { + size_t last = v.size() - 1; + for (size_t i = 0; i < last; ++i) { + write_complex_value(v[i]); + *output_ << ", "; + } + write_complex_value(v[last]); + } + *output_ << " ]"; } /** @@ -425,8 +454,9 @@ class json_writer { * @param vec Eigen Vector to write. */ void write(const std::string& key, const Eigen::VectorXd& vec) { - if (output_ == nullptr) + if (output_ == nullptr) { return; + } write_sep(); write_key(key); write_eigen_vector(vec); @@ -438,8 +468,9 @@ class json_writer { * @param vec Eigen Vector to write. */ void write(const std::string& key, const Eigen::RowVectorXd& vec) { - if (output_ == nullptr) + if (output_ == nullptr) { return; + } write_sep(); write_key(key); write_eigen_vector(vec); @@ -451,8 +482,9 @@ class json_writer { * @param mat Eigen Matrix to write. */ void write(const std::string& key, const Eigen::MatrixXd& mat) { - if (output_ == nullptr) + if (output_ == nullptr) { return; + } write_sep(); write_key(key); *output_ << "[ "; diff --git a/src/stan/callbacks/structured_writer.hpp b/src/stan/callbacks/structured_writer.hpp index b3f34857d9..fae1aed081 100644 --- a/src/stan/callbacks/structured_writer.hpp +++ b/src/stan/callbacks/structured_writer.hpp @@ -29,23 +29,13 @@ class structured_writer { * Writes key followed by start token of a structured record. * @param[in] key The name of the record. */ - virtual void begin_record(const std::string& key, bool newline = false) {} + virtual void begin_record(const std::string& key) {} /** * Writes end token of a structured record. */ virtual void end_record() {} - /** - * Writes start token of a list. - */ - virtual void begin_list() {} - - /** - * Writes end token of a list. - */ - virtual void end_list() {} - /** * Write a key-value pair to the output stream with a value of null as the * value. @@ -60,11 +50,6 @@ class structured_writer { */ virtual void write(const std::string& key, const std::string& value) {} - /** - * No-op - */ - virtual void write() {} - /** * Write a key-value pair where the value is a bool. * @param key Name of the value pair @@ -86,6 +71,22 @@ class structured_writer { */ virtual void write(const std::string& key, std::size_t value) {} + /** + * Write a key-value pair where the value is an `long long int`. + * @param key Name of the value pair + * @param value `long long int` to write. + */ + virtual void write(const std::string& key, + long long int value // NOLINT(runtime/int) + ) {} + + /** + * Write a key-value pair where the value is an `unsigned int`. + * @param key Name of the value pair + * @param value `unsigned int` to write. + */ + virtual void write(const std::string& key, unsigned int value) {} + /** * Write a key-value pair where the value is a double. * @param key Name of the value pair @@ -114,40 +115,51 @@ class structured_writer { * @param key Name of the value pair * @param values vector of strings to write. */ - void write(const std::string& key, const std::vector& values) {} + virtual void write(const std::string& key, + const std::vector& values) {} + + /** + * Write a key-value pair where the value is a vector to be made a list. + * @param key Name of the value pair + * @param values vector to write. + */ + virtual void write(const std::string& key, + const std::vector>& v) {} + + /** + * Write a key-value pair where the value is a vector to be made a list. + * @param key Name of the value pair + * @param values vector to write. + */ + virtual void write(const std::string& key, const std::vector& v) {} /** * Write a key-value pair where the value is an Eigen Matrix. * @param key Name of the value pair * @param mat Eigen Matrix to write. */ - void write(const std::string& key, const Eigen::MatrixXd& mat) {} + virtual void write(const std::string& key, const Eigen::MatrixXd& mat) {} /** * Write a key-value pair where the value is an Eigen Vector. * @param key Name of the value pair * @param vec Eigen Vector to write. */ - void write(const std::string& key, const Eigen::VectorXd& vec) {} + virtual void write(const std::string& key, const Eigen::VectorXd& vec) {} /** * Write a key-value pair where the value is a Eigen RowVector. * @param key Name of the value pair * @param vec Eigen RowVector to write. */ - void write(const std::string& key, const Eigen::RowVectorXd& vec) {} + virtual void write(const std::string& key, const Eigen::RowVectorXd& vec) {} /** * Write a key-value pair where the value is a const char*. * @param key Name of the value pair * @param value pointer to chars to write. */ - void write(const std::string& key, const char* value) {} - - /** - * Reset state - */ - virtual void reset() {} + virtual void write(const std::string& key, const char* value) {} }; } // namespace callbacks diff --git a/src/test/unit/callbacks/json_writer_test.cpp b/src/test/unit/callbacks/json_writer_test.cpp index 71893db858..bdeb05de5b 100644 --- a/src/test/unit/callbacks/json_writer_test.cpp +++ b/src/test/unit/callbacks/json_writer_test.cpp @@ -23,10 +23,29 @@ class StanInterfaceCallbacksJsonWriter : public ::testing::Test { stan::callbacks::json_writer writer; }; +bool is_whitespace(char c) { return c == ' ' || c == '\n'; } + +std::string output_sans_whitespace(std::stringstream& ss) { + auto out = ss.str(); + out.erase(std::remove_if(out.begin(), out.end(), is_whitespace), out.end()); + return out; +} + TEST_F(StanInterfaceCallbacksJsonWriter, begin_end_record) { writer.begin_record(); writer.end_record(); - EXPECT_EQ("{}\n", ss.str()); + auto out = output_sans_whitespace(ss); + EXPECT_EQ("{}", out); +} + +TEST_F(StanInterfaceCallbacksJsonWriter, begin_end_named_record) { + writer.begin_record(); + writer.begin_record("name"); + writer.end_record(); + writer.write("dummy"); + writer.end_record(); + auto out = output_sans_whitespace(ss); + EXPECT_EQ("{\"name\":{},\"dummy\":null}", out); } TEST_F(StanInterfaceCallbacksJsonWriter, begin_end_record_nested) { @@ -48,12 +67,26 @@ TEST_F(StanInterfaceCallbacksJsonWriter, begin_end_record_nested) { writer.end_record(); // 2.2 writer.end_record(); // 2 writer.end_record(); - EXPECT_EQ( - "{\"1\" : {\"key\" : \"value\"},\n" - "\"2\" : {\"key\" : \"value\"," - " \"2.1\" : {\"key\" : \"value\", \"key\" : \"value\"},\n" - "\"2.2\" : {\"key\" : \"value\", \"key\" : \"value\"}}}\n", - ss.str()); + // one whitespace-sensitive test to show formatting + const char* expected = R"json( +{ + "1" : { + "key" : "value" + }, + "2" : { + "key" : "value", + "2.1" : { + "key" : "value", + "key" : "value" + }, + "2.2" : { + "key" : "value", + "key" : "value" + } + } +} +)json"; + EXPECT_EQ(expected, ss.str()); rapidjson::Document document; ASSERT_FALSE(document.Parse<0>(ss.str().c_str()).HasParseError()); } @@ -66,20 +99,23 @@ TEST_F(StanInterfaceCallbacksJsonWriter, write_double_vector) { x.push_back(n); writer.write(key, x); - EXPECT_EQ("\"key\" : [ 0, 1, 2, 3, 4 ]", ss.str()); + auto out = output_sans_whitespace(ss); + EXPECT_EQ("\"key\":[0,1,2,3,4]", out); writer.write(key, x); + out = output_sans_whitespace(ss); EXPECT_EQ( - "\"key\" : [ 0, 1, 2, 3, 4 ]" - ", \"key\" : [ 0, 1, 2, 3, 4 ]", - ss.str()); + "\"key\":[0,1,2,3,4]" + ",\"key\":[0,1,2,3,4]", + out); writer.write(key, x); + out = output_sans_whitespace(ss); EXPECT_EQ( - "\"key\" : [ 0, 1, 2, 3, 4 ]" - ", \"key\" : [ 0, 1, 2, 3, 4 ]" - ", \"key\" : [ 0, 1, 2, 3, 4 ]", - ss.str()); + "\"key\":[0,1,2,3,4]" + ",\"key\":[0,1,2,3,4]" + ",\"key\":[0,1,2,3,4]", + out); } TEST_F(StanInterfaceCallbacksJsonWriter, single_member) { @@ -88,7 +124,8 @@ TEST_F(StanInterfaceCallbacksJsonWriter, single_member) { writer.begin_record(); writer.write(key, value); writer.end_record(); - EXPECT_EQ("{\"key\" : \"value\"}\n", ss.str()); + auto out = output_sans_whitespace(ss); + EXPECT_EQ("{\"key\":\"value\"}", out); } TEST_F(StanInterfaceCallbacksJsonWriter, more_members) { @@ -98,12 +135,13 @@ TEST_F(StanInterfaceCallbacksJsonWriter, more_members) { writer.write(key, value); writer.write(key, value); - EXPECT_EQ("{\"key\" : \"value\", \"key\" : \"value\"", ss.str()); + auto out = output_sans_whitespace(ss); + EXPECT_EQ("{\"key\":\"value\",\"key\":\"value\"", out); writer.write(key, value); writer.end_record(); - EXPECT_EQ("{\"key\" : \"value\", \"key\" : \"value\", \"key\" : \"value\"}\n", - ss.str()); + out = output_sans_whitespace(ss); + EXPECT_EQ("{\"key\":\"value\",\"key\":\"value\",\"key\":\"value\"}", out); } TEST_F(StanInterfaceCallbacksJsonWriter, write_double_vector_precision2) { @@ -112,7 +150,8 @@ TEST_F(StanInterfaceCallbacksJsonWriter, write_double_vector_precision2) { const int N = 5; std::vector x{1.23456789, 2.3456789, 3.45678910, 4.567890123}; writer.write(key, x); - EXPECT_EQ("\"key\" : [ 1.2, 2.3, 3.5, 4.6 ]", ss.str()); + auto out = output_sans_whitespace(ss); + EXPECT_EQ("\"key\":[1.2,2.3,3.5,4.6]", out); } TEST_F(StanInterfaceCallbacksJsonWriter, write_double_vector_nan_inf) { @@ -122,7 +161,8 @@ TEST_F(StanInterfaceCallbacksJsonWriter, write_double_vector_nan_inf) { x.push_back(std::numeric_limits::infinity()); x.push_back(-std::numeric_limits::infinity()); writer.write(key, x); - EXPECT_EQ("\"key\" : [ NaN, Inf, -Inf ]", ss.str()); + auto out = output_sans_whitespace(ss); + EXPECT_EQ("\"key\":[NaN,Inf,-Inf]", out); } TEST_F(StanInterfaceCallbacksJsonWriter, write_string_special_characters) { @@ -132,7 +172,7 @@ TEST_F(StanInterfaceCallbacksJsonWriter, write_string_special_characters) { "the\\quick\"brown/\bfox\fjumped\nover\rthe\tlazy\vdog\atwotimes"); writer.write(key, x); EXPECT_EQ( - "\"key\" : " + "\n\"key\" : " "\"the\\\\quick\\\"brown\\/" "\\bfox\\fjumped\\nover\\rthe\\tlazy\\vdog\\atwotimes\"", ss.str()); @@ -142,7 +182,8 @@ TEST_F(StanInterfaceCallbacksJsonWriter, write_double_vector_precision3) { std::vector x{1.23456789, 2.3456789, 3.45678910, 4.567890123}; ss.precision(3); writer.write("key", x); - EXPECT_EQ("\"key\" : [ 1.23, 2.35, 3.46, 4.57 ]", ss.str()); + auto out = output_sans_whitespace(ss); + EXPECT_EQ("\"key\":[1.23,2.35,3.46,4.57]", out); } TEST_F(StanInterfaceCallbacksJsonWriter, write_string_vector) { @@ -152,17 +193,20 @@ TEST_F(StanInterfaceCallbacksJsonWriter, write_string_vector) { x.push_back(std::to_string(n)); writer.write("key", x); - EXPECT_EQ("\"key\" : [ 0, 1, 2, 3, 4 ]", ss.str()); + auto out = output_sans_whitespace(ss); + EXPECT_EQ("\"key\":[0,1,2,3,4]", out); } TEST_F(StanInterfaceCallbacksJsonWriter, write_null) { writer.write("message"); - EXPECT_EQ("\"message\" : null", ss.str()); + auto out = output_sans_whitespace(ss); + EXPECT_EQ("\"message\":null", out); } TEST_F(StanInterfaceCallbacksJsonWriter, write_string) { writer.write("key", "value"); - EXPECT_EQ("\"key\" : \"value\"", ss.str()); + auto out = output_sans_whitespace(ss); + EXPECT_EQ("\"key\":\"value\"", out); } TEST_F(StanInterfaceCallbacksJsonWriter, write_int_vector) { @@ -173,21 +217,24 @@ TEST_F(StanInterfaceCallbacksJsonWriter, write_int_vector) { x.push_back(n); writer.write(key, x); - EXPECT_EQ("\"key\" : [ 0, 1, 2, 3, 4 ]", ss.str()); + auto out = output_sans_whitespace(ss); + EXPECT_EQ("\"key\":[0,1,2,3,4]", out); } TEST_F(StanInterfaceCallbacksJsonWriter, write_empty_vector) { std::string key("key"); std::vector x; writer.write(key, x); - EXPECT_EQ("\"key\" : [ ]", ss.str()); + auto out = output_sans_whitespace(ss); + EXPECT_EQ("\"key\":[]", out); } TEST_F(StanInterfaceCallbacksJsonWriter, write_complex) { std::string key("key"); std::complex x(1.110, 2.110); writer.write(key, x); - EXPECT_EQ("\"key\" : [1.11, 2.11]", ss.str()); + auto out = output_sans_whitespace(ss); + EXPECT_EQ("\"key\":[1.11,2.11]", out); } TEST_F(StanInterfaceCallbacksJsonWriter, write_complex_inf) { @@ -195,7 +242,8 @@ TEST_F(StanInterfaceCallbacksJsonWriter, write_complex_inf) { std::complex x(std::numeric_limits::infinity(), -std::numeric_limits::infinity()); writer.write(key, x); - EXPECT_EQ("\"key\" : [Inf, -Inf]", ss.str()); + auto out = output_sans_whitespace(ss); + EXPECT_EQ("\"key\":[Inf,-Inf]", out); } TEST_F(StanInterfaceCallbacksJsonWriter, write_complex_vector) { @@ -206,42 +254,48 @@ TEST_F(StanInterfaceCallbacksJsonWriter, write_complex_vector) { x.push_back(std::complex(1.110, 2.110)); writer.write(key, x); - EXPECT_EQ("\"key\" : [ [1.11, 2.11], [1.11, 2.11], [1.11, 2.11] ]", ss.str()); + auto out = output_sans_whitespace(ss); + EXPECT_EQ("\"key\":[[1.11,2.11],[1.11,2.11],[1.11,2.11]]", out); } TEST_F(StanInterfaceCallbacksJsonWriter, write_eigen_vector) { std::string key("key"); Eigen::VectorXd x{{1.0, 2.0, 3.0, 4.0}}; writer.write(key, x); - EXPECT_EQ("\"key\" : [ 1, 2, 3, 4 ]", ss.str()); + auto out = output_sans_whitespace(ss); + EXPECT_EQ("\"key\":[1,2,3,4]", out); } TEST_F(StanInterfaceCallbacksJsonWriter, write_empty_eigen_vector) { std::string key("key"); Eigen::VectorXd x; writer.write(key, x); - EXPECT_EQ("\"key\" : [ ]", ss.str()); + auto out = output_sans_whitespace(ss); + EXPECT_EQ("\"key\":[]", out); } TEST_F(StanInterfaceCallbacksJsonWriter, write_eigen_rowvector) { std::string key("key"); Eigen::RowVectorXd x{{1.0, 2.0, 3.0, 4.0}}; writer.write(key, x); - EXPECT_EQ("\"key\" : [ 1, 2, 3, 4 ]", ss.str()); + auto out = output_sans_whitespace(ss); + EXPECT_EQ("\"key\":[1,2,3,4]", out); } TEST_F(StanInterfaceCallbacksJsonWriter, write_eigen_matrix) { std::string key("key"); Eigen::MatrixXd x{{1.0, 2.0}, {3.0, 4.0}}; writer.write(key, x); - EXPECT_EQ("\"key\" : [ [ 1, 2 ], [ 3, 4 ] ]", ss.str()); + auto out = output_sans_whitespace(ss); + EXPECT_EQ("\"key\":[[1,2],[3,4]]", out); } TEST_F(StanInterfaceCallbacksJsonWriter, write_empty_eigen_matrix) { std::string key("key"); Eigen::MatrixXd x; writer.write(key, x); - EXPECT_EQ("\"key\" : [ ]", ss.str()); + auto out = output_sans_whitespace(ss); + EXPECT_EQ("\"key\":[]", out); } TEST_F(StanInterfaceCallbacksJsonWriter, no_op_writer) {