kphp icon indicating copy to clipboard operation
kphp copied to clipboard

implement support for JSON_PRETTY_PRINT flag for json_encode()

Open comm644 opened this issue 2 years ago • 1 comments

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.

comm644 avatar Apr 11 '22 20:04 comm644

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();

comm644 avatar Apr 24 '22 21:04 comm644