kphp
kphp copied to clipboard
implement support for JSON_PRETTY_PRINT flag for json_encode()
Hello, everybody!
Problem: I would like to reformat user-defined JSON definition. For this I use decode+encode pair:
$outToUser = json_encode(
json_decode( $userEnteredJson, true),
JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
but json_encode() does not accept JSON_PRETTY_PRINT , as result instead pretty formatted text for editing I have some not editable garbage.
Solution: implement support for JSON_PRETTY_PRINT flag.
By the way fixed float point encoding.
patch:
diff --git a/runtime/json-functions.cpp b/runtime/json-functions.cpp
index ff2774ea..954af817 100644
--- a/runtime/json-functions.cpp
+++ b/runtime/json-functions.cpp
@@ -9,8 +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 <locale.h>
+#include <stdlib.h>
+
+
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');
@@ -214,13 +239,15 @@ bool do_json_encode_string_vkext(const char *s, int len) noexcept {
} // namespace
namespace impl_ {
+string JsonEncoder::pretty = string((const char*)" ");
JsonEncoder::JsonEncoder(int64_t options, bool simple_encode) noexcept:
options_(options),
simple_encode_(simple_encode) {
}
-bool JsonEncoder::encode(bool b) const noexcept {
+bool JsonEncoder::encode(bool b, const string prefix __attribute__((unused))) const noexcept {
+
if (b) {
static_SB.append("true", 4);
} else {
@@ -234,12 +261,12 @@ bool JsonEncoder::encode_null() const noexcept {
return true;
}
-bool JsonEncoder::encode(int64_t i) const noexcept {
+bool JsonEncoder::encode(int64_t i, const string prefix __attribute__((unused))) const noexcept {
static_SB << i;
return true;
}
-bool JsonEncoder::encode(double d) const noexcept {
+bool JsonEncoder::encode(double d, const string prefix __attribute__((unused))) const noexcept {
if (vk::any_of_equal(std::fpclassify(d), FP_INFINITE, FP_NAN)) {
php_warning("strange double %lf in function json_encode", d);
if (options_ & JSON_PARTIAL_OUTPUT_ON_ERROR) {
@@ -248,29 +275,30 @@ bool JsonEncoder::encode(double d) const noexcept {
return false;
}
} else {
- static_SB << (simple_encode_ ? f$number_format(d, 6, string{"."}, string{}) : string{d});
+// static_SB << (simple_encode_ ? f$number_format(d, 6, string{"."}, string{}) : string{d});
+ static_SB << f$number_format(d, 6, string{"."}, string{});
}
return true;
}
-bool JsonEncoder::encode(const string &s) const noexcept {
+bool JsonEncoder::encode(const string &s, const string prefix __attribute__((unused))) const noexcept {
return simple_encode_ ? do_json_encode_string_vkext(s.c_str(), s.size()) : do_json_encode_string_php(s.c_str(), s.size(), options_);
}
-bool JsonEncoder::encode(const mixed &v) const noexcept {
+bool JsonEncoder::encode(const mixed &v, const string prefix) const 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();
}
@@ -507,7 +535,7 @@ bool do_json_decode(const char *s, int s_len, int &i, mixed &v) noexcept {
}
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 cd8c9d5d..e6560fb2 100644
--- a/runtime/json-functions.h
+++ b/runtime/json-functions.h
@@ -11,7 +11,7 @@ constexpr int64_t JSON_UNESCAPED_UNICODE = 1;
constexpr int64_t JSON_FORCE_OBJECT = 16;
constexpr int64_t JSON_PRETTY_PRINT = 128; // TODO: add actual support
constexpr int64_t JSON_PARTIAL_OUTPUT_ON_ERROR = 512;
-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;
namespace impl_ {
@@ -19,27 +19,31 @@ class JsonEncoder : vk::not_copyable {
public:
JsonEncoder(int64_t options, bool simple_encode) noexcept;
- bool encode(bool b) const noexcept;
- bool encode(int64_t i) const noexcept;
- bool encode(double d) const noexcept;
- bool encode(const string &s) const noexcept;
- bool encode(const mixed &v) const noexcept;
+ bool encode(bool b, const string prefix=string()) const noexcept;
+ bool encode(int64_t i, const string prefix=string()) const noexcept;
+ bool encode(double d, const string prefix=string()) const noexcept;
+ bool encode(const string &s, const string prefix=string()) const noexcept;
+ bool encode(const mixed &v, const string prefix=string()) const noexcept;
template<class T>
- bool encode(const array<T> &arr) const noexcept;
+ bool encode(const array<T> &arr, const string prefix=string()) const noexcept;
template<class T>
- bool encode(const Optional<T> &opt) const noexcept;
+ bool encode(const Optional<T> &opt, const string prefix=string()) const noexcept;
private:
bool encode_null() const noexcept;
const int64_t options_{0};
const bool simple_encode_{false};
+ static string pretty;
};
template<class T>
-bool JsonEncoder::encode(const array<T> &arr) const noexcept {
+bool JsonEncoder::encode(const array<T> &arr, const string prefix) const noexcept {
+ string next_prefix = prefix;
+
+
bool is_vector = arr.is_vector();
const bool force_object = static_cast<bool>(JSON_FORCE_OBJECT & options_);
if (!force_object && !is_vector && arr.is_pseudo_vector()) {
@@ -54,17 +58,32 @@ bool JsonEncoder::encode(const array<T> &arr) const noexcept {
static_SB << "{["[is_vector];
bool first = true;
+ if ( (options_ & JSON_PRETTY_PRINT) && !is_vector) {
+ next_prefix.append(pretty);
+ static_SB << '\n';
+ }
+
for (auto p : arr) {
if (!first) {
static_SB << ',';
+ if ( (options_ & JSON_PRETTY_PRINT) && !is_vector) {
+ static_SB << '\n';
+ }
}
first = false;
+
+
if (!is_vector) {
+ if ( (options_ & JSON_PRETTY_PRINT) && !is_vector) {
+ static_SB << next_prefix;
+ }
+
const auto key = p.get_key();
if (array<T>::is_int_key(key)) {
static_SB << '"' << key.to_int() << '"';
} else {
+
if (!encode(key)) {
if (!(options_ & JSON_PARTIAL_OUTPUT_ON_ERROR)) {
return false;
@@ -74,19 +93,24 @@ bool JsonEncoder::encode(const array<T> &arr) const noexcept {
static_SB << ':';
}
- if (!encode(p.get_value())) {
+ if (!encode(p.get_value(), next_prefix)) {
if (!(options_ & JSON_PARTIAL_OUTPUT_ON_ERROR)) {
return false;
}
}
}
+
+ if ( (options_ & JSON_PRETTY_PRINT) && !is_vector) {
+ static_SB << '\n';
+ static_SB << prefix;
+ }
static_SB << "}]"[is_vector];
return true;
}
template<class T>
-bool JsonEncoder::encode(const Optional<T> &opt) const noexcept {
+bool JsonEncoder::encode(const Optional<T> &opt, const string prefix) const noexcept {
switch (opt.value_state()) {
case OptionalState::has_value:
return encode(opt.val());
@@ -109,7 +133,8 @@ Optional<string> 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();