diff --git a/runtime/json-functions.cpp b/runtime/json-functions.cpp index 79c958ca04..551018f665 100644 --- a/runtime/json-functions.cpp +++ b/runtime/json-functions.cpp @@ -9,10 +9,33 @@ #include "runtime/exception.h" #include "runtime/string_functions.h" +#ifndef _GNU_SOURCE +#define _GNU_SOURCE // needed to unlock the strtod_l +#endif +#include +#include + // note: json-functions.cpp is used for non-typed json implementation: for json_encode() and json_decode() // for classes, e.g. `JsonEncoder::encode(new A)`, see json-writer.cpp and from/to visitors namespace { +//from https://github.com/kgabis/parson/issues/98 +// Cache locale object +static int c_locale_initialized = 0; +static locale_t c_locale; + +locale_t get_c_locale() +{ + if(!c_locale_initialized) + { + c_locale_initialized = 1; + c_locale = newlocale(LC_ALL_MASK, "C", NULL); + } + return c_locale; +} + + + void json_append_one_char(unsigned int c) noexcept { static_SB.append_char('\\'); static_SB.append_char('u'); @@ -215,6 +238,10 @@ bool do_json_encode_string_vkext(const char *s, int len) noexcept { } // namespace +namespace impl_ { +string JsonEncoder::pretty = string((const char*)" "); +} + string JsonPath::to_string() const { // this function is called only when error is occurred, so it's not // very performance-sensitive @@ -252,7 +279,7 @@ JsonEncoder::JsonEncoder(int64_t options, bool simple_encode, const char *json_o json_obj_magic_key_(json_obj_magic_key) { } -bool JsonEncoder::encode(bool b) noexcept { +bool JsonEncoder::encode(bool b, const string prefix __attribute__((unused))) noexcept { if (b) { static_SB.append("true", 4); } else { @@ -266,12 +293,12 @@ bool JsonEncoder::encode_null() const noexcept { return true; } -bool JsonEncoder::encode(int64_t i) noexcept { +bool JsonEncoder::encode(int64_t i, const string prefix __attribute__((unused))) noexcept { static_SB << i; return true; } -bool JsonEncoder::encode(double d) noexcept { +bool JsonEncoder::encode(double d, const string prefix __attribute__((unused))) noexcept { if (vk::any_of_equal(std::fpclassify(d), FP_INFINITE, FP_NAN)) { php_warning("%s: strange double %lf in function json_encode", json_path_.to_string().c_str(), d); if (options_ & JSON_PARTIAL_OUTPUT_ON_ERROR) { @@ -280,29 +307,29 @@ bool JsonEncoder::encode(double d) noexcept { return false; } } else { - static_SB << (simple_encode_ ? f$number_format(d, 6, string{"."}, string{}) : string{d}); + static_SB << f$number_format(d, 6, string{"."}, string{}); //always format with dot } return true; } -bool JsonEncoder::encode(const string &s) noexcept { +bool JsonEncoder::encode(const string &s, const string prefix __attribute__((unused))) noexcept { return simple_encode_ ? do_json_encode_string_vkext(s.c_str(), s.size()) : do_json_encode_string_php(json_path_, s.c_str(), s.size(), options_); } -bool JsonEncoder::encode(const mixed &v) noexcept { +bool JsonEncoder::encode(const mixed &v, const string prefix) noexcept { switch (v.get_type()) { case mixed::type::NUL: return encode_null(); case mixed::type::BOOLEAN: - return encode(v.as_bool()); + return encode(v.as_bool(), prefix); case mixed::type::INTEGER: - return encode(v.as_int()); + return encode(v.as_int(), prefix); case mixed::type::FLOAT: - return encode(v.as_double()); + return encode(v.as_double(), prefix); case mixed::type::STRING: - return encode(v.as_string()); + return encode(v.as_string(), prefix); case mixed::type::ARRAY: - return encode(v.as_array()); + return encode(v.as_array(), prefix); default: __builtin_unreachable(); } @@ -545,7 +572,7 @@ bool do_json_decode(const char *s, int s_len, int &i, mixed &v, const char *json } char *end_ptr; - double floatval = strtod(s + i, &end_ptr); + double floatval = strtod_l(s + i, &end_ptr, get_c_locale()); if (end_ptr == s + j) { i = j; new(&v) mixed(floatval); diff --git a/runtime/json-functions.h b/runtime/json-functions.h index 8754d73dd5..8ebd9a6b28 100644 --- a/runtime/json-functions.h +++ b/runtime/json-functions.h @@ -15,7 +15,7 @@ constexpr int64_t JSON_PRETTY_PRINT = 128; // TODO: add actual support to untype constexpr int64_t JSON_PARTIAL_OUTPUT_ON_ERROR = 512; constexpr int64_t JSON_PRESERVE_ZERO_FRACTION = 1024; -constexpr int64_t JSON_AVAILABLE_OPTIONS = JSON_UNESCAPED_UNICODE | JSON_FORCE_OBJECT | JSON_PARTIAL_OUTPUT_ON_ERROR; +constexpr int64_t JSON_AVAILABLE_OPTIONS = JSON_UNESCAPED_UNICODE | JSON_FORCE_OBJECT | JSON_PARTIAL_OUTPUT_ON_ERROR | JSON_PRETTY_PRINT; constexpr int64_t JSON_AVAILABLE_FLAGS_TYPED = JSON_PRETTY_PRINT | JSON_PRESERVE_ZERO_FRACTION; struct JsonPath { @@ -46,17 +46,17 @@ class JsonEncoder : vk::not_copyable { public: JsonEncoder(int64_t options, bool simple_encode, const char *json_obj_magic_key = nullptr) noexcept; - bool encode(bool b) noexcept; - bool encode(int64_t i) noexcept; - bool encode(const string &s) noexcept; - bool encode(double d) noexcept; - bool encode(const mixed &v) noexcept; + bool encode(bool b, const string prefix=string()) noexcept; + bool encode(int64_t i, const string prefix=string()) noexcept; + bool encode(double d, const string prefix=string()) noexcept; + bool encode(const string &s, const string prefix=string()) noexcept; + bool encode(const mixed &v, const string prefix=string()) noexcept; template - bool encode(const array &arr) noexcept; + bool encode(const array &arr, const string prefix=string()) noexcept; template - bool encode(const Optional &opt) noexcept; + bool encode(const Optional &opt, const string prefix=string()) noexcept; private: bool encode_null() const noexcept; @@ -65,10 +65,12 @@ class JsonEncoder : vk::not_copyable { const int64_t options_{0}; const bool simple_encode_{false}; const char *json_obj_magic_key_{nullptr}; + static string pretty; }; template -bool JsonEncoder::encode(const array &arr) noexcept { +bool JsonEncoder::encode(const array &arr, const string prefix) noexcept { + string next_prefix = prefix; bool is_vector = arr.is_vector(); const bool force_object = static_cast(JSON_FORCE_OBJECT & options_); if (!force_object && !is_vector && arr.is_pseudo_vector()) { @@ -88,6 +90,10 @@ bool JsonEncoder::encode(const array &arr) noexcept { for (auto p : arr) { if (i != 0) { static_SB << ','; + if ( (options_ & JSON_PRETTY_PRINT) && !is_vector) { + next_prefix.append(pretty); + static_SB << '\n'; + } } if (!encode(p.get_value())) { if (!(options_ & JSON_PARTIAL_OUTPUT_ON_ERROR)) { @@ -99,9 +105,16 @@ bool JsonEncoder::encode(const array &arr) noexcept { json_path_.leave(); } else { bool is_first = true; + if (options_ & JSON_PRETTY_PRINT) { + next_prefix.append(pretty); + static_SB << '\n' << next_prefix; + } for (auto p : arr) { if (!is_first) { static_SB << ','; + if ( (options_ & JSON_PRETTY_PRINT)) { + static_SB << '\n' << next_prefix; + } } is_first = false; const char *next_key = nullptr; @@ -125,7 +138,7 @@ bool JsonEncoder::encode(const array &arr) noexcept { } static_SB << ':'; json_path_.enter(next_key); - if (!encode(p.get_value())) { + if (!encode(p.get_value(), next_prefix)) { if (!(options_ & JSON_PARTIAL_OUTPUT_ON_ERROR)) { return false; } @@ -134,12 +147,17 @@ bool JsonEncoder::encode(const array &arr) noexcept { } } + if ( (options_ & JSON_PRETTY_PRINT) && !is_vector) { + static_SB << '\n'; + static_SB << prefix; + } + static_SB << "}]"[is_vector]; return true; } template -bool JsonEncoder::encode(const Optional &opt) noexcept { +bool JsonEncoder::encode(const Optional &opt, const string prefix) noexcept { switch (opt.value_state()) { case OptionalState::has_value: return encode(opt.val()); @@ -162,7 +180,8 @@ Optional f$json_encode(const T &v, int64_t options = 0, bool simple_enco } static_SB.clean(); - if (unlikely(!impl_::JsonEncoder(options, simple_encode).encode(v))) { + string prefix = string(); + if (unlikely(!impl_::JsonEncoder(options, simple_encode).encode(v, prefix))) { return false; } return static_SB.str();