Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 39 additions & 12 deletions runtime/json-functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <locale.h>
#include <stdlib.h>

// 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');
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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) {
Expand All @@ -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();
}
Expand Down Expand Up @@ -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);
Expand Down
43 changes: 31 additions & 12 deletions runtime/json-functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<class T>
bool encode(const array<T> &arr) noexcept;
bool encode(const array<T> &arr, const string prefix=string()) noexcept;

template<class T>
bool encode(const Optional<T> &opt) noexcept;
bool encode(const Optional<T> &opt, const string prefix=string()) noexcept;

private:
bool encode_null() const noexcept;
Expand All @@ -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<class T>
bool JsonEncoder::encode(const array<T> &arr) noexcept {
bool JsonEncoder::encode(const array<T> &arr, const string prefix) 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()) {
Expand All @@ -88,6 +90,10 @@ bool JsonEncoder::encode(const array<T> &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)) {
Expand All @@ -99,9 +105,16 @@ bool JsonEncoder::encode(const array<T> &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;
Expand All @@ -125,7 +138,7 @@ bool JsonEncoder::encode(const array<T> &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;
}
Expand All @@ -134,12 +147,17 @@ bool JsonEncoder::encode(const array<T> &arr) noexcept {
}
}

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) noexcept {
bool JsonEncoder::encode(const Optional<T> &opt, const string prefix) noexcept {
switch (opt.value_state()) {
case OptionalState::has_value:
return encode(opt.val());
Expand All @@ -162,7 +180,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();
Expand Down