* Copyright (c) 2024 Huawei Technologies Co., Ltd.
* openUBMC is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
* @file json.cpp
* @brief 实现JSON编解码功能
*/
#include <iomanip>
#include <mc/dict.h>
#include <mc/exception.h>
#include <mc/json.h>
#include <mc/variant.h>
#include <sstream>
namespace mc {
namespace json {
class encoder {
public:
explicit encoder(
const json_encode_options& options = json_encode_options::default_encode_options)
: m_stream(), m_options(options), m_current_depth(0) {
m_options.normalize();
if (m_options.float_precision >= 0) {
m_stream.precision(m_options.float_precision);
} else {
m_stream.precision(std::numeric_limits<double>::max_digits10);
}
}
std::string encode(const variant& value) {
encode_value(value);
return m_stream.str();
}
void check_depth() {
if (m_current_depth >= m_options.max_depth) {
MC_THROW(mc::parse_error_exception, "JSON nesting depth exceeds limit");
}
}
void enter_scope() {
++m_current_depth;
check_depth();
}
void leave_scope() {
--m_current_depth;
}
void add_indent() {
if (m_options.pretty_print) {
m_stream << '\n'
<< std::string(m_current_depth * m_options.indent_size, ' ');
}
}
void add_separator() {
if (m_options.pretty_print) {
m_stream << ' ';
}
}
void encode_string(std::string_view str) {
m_stream << '"';
for (unsigned char c : str) {
switch (c) {
case '"':
m_stream << "\\\"";
break;
case '\\':
m_stream << "\\\\";
break;
case '\b':
m_stream << "\\b";
break;
case '\f':
m_stream << "\\f";
break;
case '\n':
m_stream << "\\n";
break;
case '\r':
m_stream << "\\r";
break;
case '\t':
m_stream << "\\t";
break;
default:
if (c < 0x20) {
m_stream << "\\u" << std::hex << std::setw(4) << std::setfill('0')
<< static_cast<int>(c);
} else if (m_options.escape_non_ascii && c >= 0x80) {
m_stream << "\\u" << std::hex << std::setw(4) << std::setfill('0')
<< static_cast<int>(c);
} else {
m_stream << c;
}
}
}
m_stream << '"';
}
void encode_number(double num) {
if (std::isfinite(num)) {
if (m_options.float_precision >= 0) {
m_stream << std::fixed << std::setprecision(m_options.float_precision) << num;
} else {
m_stream << std::defaultfloat << num;
}
} else {
MC_THROW(mc::parse_error_exception, "Invalid number");
}
}
void encode_array(const variants& arr) {
enter_scope();
m_stream << '[';
bool first = true;
for (const auto& item : arr) {
if (!first) {
m_stream << ',';
add_separator();
}
if (m_options.pretty_print && !first) {
add_indent();
}
encode_value(item);
first = false;
}
if (m_options.pretty_print && !first) {
add_indent();
}
m_stream << ']';
leave_scope();
}
void encode_object(const dict& obj) {
enter_scope();
m_stream << '{';
std::vector<std::reference_wrapper<const std::string>> keys;
keys.reserve(obj.size());
for (const auto& entry : obj) {
keys.push_back(std::cref(entry.key.get_string()));
}
if (m_options.sort_keys) {
std::sort(keys.begin(), keys.end(), [](const auto& a, const auto& b) {
return a.get() < b.get();
});
}
bool first = true;
for (const auto& key_ref : keys) {
const auto& key = key_ref.get();
if (!first) {
m_stream << ',';
add_separator();
}
if (m_options.pretty_print && !first) {
add_indent();
}
encode_string(key);
m_stream << ':';
add_separator();
encode_value(obj[key]);
first = false;
}
if (m_options.pretty_print && !first) {
add_indent();
}
m_stream << '}';
leave_scope();
}
void encode_value(const variant& value) {
value.visit_with([this](auto&& v) {
using T = std::decay_t<decltype(v)>;
if constexpr (std::is_same_v<T, std::nullptr_t>) {
m_stream << "null";
} else if constexpr (std::is_same_v<T, bool>) {
m_stream << (v ? "true" : "false");
} else if constexpr (std::is_integral_v<T>) {
m_stream << v;
} else if constexpr (std::is_floating_point_v<T>) {
encode_number(v);
} else if constexpr (std::is_same_v<T, typename variant::string_type>) {
encode_string(v);
} else if constexpr (std::is_same_v<T, typename variant::blob_type>) {
encode_string(v.as_string_view());
} else if constexpr (std::is_same_v<T, typename variant::array_type>) {
encode_array(v);
} else if constexpr (std::is_same_v<T, typename variant::object_type>) {
encode_object(v);
} else if constexpr (std::is_same_v<T, typename variant::extension_type>) {
encode_string(v.as_string());
} else {
MC_THROW(mc::parse_error_exception, "Unsupported JSON value type ${type}",
("type", mc::pretty_name<T>()));
}
});
}
std::string get_result() const {
return m_stream.str();
}
private:
std::ostringstream m_stream;
json_encode_options m_options;
int m_current_depth;
};
class decoder {
public:
explicit decoder(std::string_view json, const json_decode_options& options =
json_decode_options::default_decode_options)
: m_input(json), m_pos(0), m_options(options), m_current_depth(0) {
m_options.normalize();
if (m_input.length() > m_options.max_input_length) {
MC_THROW(mc::parse_error_exception, "Input JSON string length exceeds limit");
}
}
variant decode() {
skip_whitespace();
variant result = parse_value();
skip_whitespace();
if (m_pos < m_input.length()) {
MC_THROW(mc::parse_error_exception, "Extra characters after JSON string");
}
return result;
}
void skip_whitespace() {
while (m_pos < m_input.length() && std::isspace(m_input[m_pos])) {
++m_pos;
}
}
bool is_eof() const {
return m_pos >= m_input.length();
}
char current() const {
return is_eof() ? '\0' : m_input[m_pos];
}
void advance() {
if (!is_eof()) {
++m_pos;
}
}
void check_depth() {
if (m_current_depth >= m_options.max_depth) {
MC_THROW(mc::parse_error_exception, "JSON nesting depth exceeds limit");
}
}
void enter_scope() {
++m_current_depth;
check_depth();
}
void leave_scope() {
--m_current_depth;
}
variant parse_value() {
skip_whitespace();
char c = current();
switch (c) {
case 'n':
return parse_null();
case 't':
return parse_true();
case 'f':
return parse_false();
case '"':
return parse_string();
case '[':
return parse_array();
case '{':
return parse_object();
default:
if (c == '-' || std::isdigit(c)) {
return parse_number();
}
MC_THROW(mc::parse_error_exception, "Invalid JSON value");
}
}
variant parse_null() {
if (m_input.substr(m_pos, 4) == "null") {
m_pos += 4;
return variant();
}
MC_THROW(mc::parse_error_exception, "Invalid null value");
}
variant parse_true() {
if (m_input.substr(m_pos, 4) == "true") {
m_pos += 4;
return variant(true);
}
MC_THROW(mc::parse_error_exception, "Invalid true value");
}
variant parse_false() {
if (m_input.substr(m_pos, 5) == "false") {
m_pos += 5;
return variant(false);
}
MC_THROW(mc::parse_error_exception, "Invalid false value");
}
void handle_unicode_escape(std::string& result) {
if (m_pos + 4 >= m_input.length()) {
MC_THROW(mc::parse_error_exception, "Invalid Unicode escape sequence");
}
std::string hex = std::string(m_input.substr(m_pos + 1, 4));
for (char c : hex) {
if (!std::isxdigit(c)) {
MC_THROW(mc::parse_error_exception, "Invalid Unicode escape sequence");
}
}
try {
int code_point = std::stoi(hex, nullptr, 16);
if (code_point <= 0x7F) {
result += static_cast<char>(code_point);
} else if (code_point <= 0x7FF) {
result += static_cast<char>(0xC0 | (code_point >> 6));
result += static_cast<char>(0x80 | (code_point & 0x3F));
} else if (code_point <= 0xFFFF) {
result += static_cast<char>(0xE0 | (code_point >> 12));
result += static_cast<char>(0x80 | ((code_point >> 6) & 0x3F));
result += static_cast<char>(0x80 | (code_point & 0x3F));
} else {
MC_THROW(mc::parse_error_exception, "Unsupported Unicode character");
}
m_pos += 4;
} catch (const std::exception&) {
MC_THROW(mc::parse_error_exception, "Invalid Unicode escape sequence");
}
}
void handle_normal_char(char c, std::string& result) {
if (static_cast<unsigned char>(c) < 0x20) {
MC_THROW(mc::parse_error_exception, "String contains invalid character");
}
result += c;
}
void handle_escape_sequence(std::string& result) {
if (is_eof()) {
MC_THROW(mc::parse_error_exception, "String not properly terminated");
}
char c = current();
switch (c) {
case '"':
result += '"';
break;
case '\\':
result += '\\';
break;
case '/':
result += '/';
break;
case 'b':
result += '\b';
break;
case 'f':
result += '\f';
break;
case 'n':
result += '\n';
break;
case 'r':
result += '\r';
break;
case 't':
result += '\t';
break;
case 'u':
handle_unicode_escape(result);
break;
default:
MC_THROW(mc::parse_error_exception, "Invalid escape sequence");
}
}
variant parse_string() {
advance();
std::string result;
while (!is_eof()) {
char c = current();
if (c == '"') {
advance();
if (result.length() > m_options.max_string_length) {
MC_THROW(mc::parse_error_exception, "String length exceeds limit");
}
return variant(result);
}
if (c == '\\') {
advance();
handle_escape_sequence(result);
} else {
handle_normal_char(c, result);
}
advance();
}
MC_THROW(mc::parse_error_exception, "String not properly terminated");
}
variant parse_number() {
size_t start = m_pos;
bool is_float = false;
bool is_negative = false;
if (current() == '-') {
is_negative = true;
advance();
}
if (current() == '0') {
advance();
} else if (std::isdigit(current())) {
while (!is_eof() && std::isdigit(current())) {
advance();
}
} else {
MC_THROW(mc::parse_error_exception, "Invalid number format");
}
if (current() == '.') {
is_float = true;
advance();
if (!std::isdigit(current())) {
MC_THROW(mc::parse_error_exception, "Decimal point must be followed by digits");
}
while (!is_eof() && std::isdigit(current())) {
advance();
}
}
if (current() == 'e' || current() == 'E') {
is_float = true;
advance();
if (current() == '+' || current() == '-') {
advance();
}
if (!std::isdigit(current())) {
MC_THROW(mc::parse_error_exception, "Exponent part must contain digits");
}
while (!is_eof() && std::isdigit(current())) {
advance();
}
}
std::string number_str(m_input.substr(start, m_pos - start));
try {
if (is_float) {
return variant(std::stod(number_str));
} else if (is_negative) {
return variant(std::stoll(number_str));
} else {
return variant(std::stoull(number_str));
}
} catch (const std::exception&) {
MC_THROW(mc::parse_error_exception, "Number conversion failed");
}
}
variant parse_array() {
enter_scope();
advance();
variants result;
skip_whitespace();
if (current() == ']') {
advance();
leave_scope();
return variant(result);
}
while (true) {
if (result.size() >= m_options.max_array_size) {
MC_THROW(mc::parse_error_exception, "Array element count exceeds limit");
}
result.push_back(parse_value());
skip_whitespace();
if (current() == ']') {
advance();
leave_scope();
return variant(result);
}
if (current() != ',') {
MC_THROW(mc::parse_error_exception, "Array elements must be separated by commas");
}
advance();
skip_whitespace();
}
}
variant parse_object() {
enter_scope();
advance();
mc::dict result;
skip_whitespace();
if (current() == '}') {
advance();
leave_scope();
return variant(result);
}
size_t count = 0;
while (true) {
if (count >= m_options.max_object_size) {
MC_THROW(mc::parse_error_exception, "Object key-value pair count exceeds limit");
}
count++;
if (current() != '"') {
MC_THROW(mc::parse_error_exception, "Object key must be a string");
}
std::string key = parse_string().as<std::string>();
skip_whitespace();
if (current() != ':') {
MC_THROW(mc::parse_error_exception, "Object key-value pairs must be separated by colons");
}
advance();
variant value = parse_value();
result(key, value);
skip_whitespace();
if (current() == '}') {
advance();
leave_scope();
return variant(result);
}
if (current() != ',') {
MC_THROW(mc::parse_error_exception, "Object key-value pairs must be separated by commas");
}
advance();
skip_whitespace();
}
}
private:
std::string_view m_input;
size_t m_pos;
json_decode_options m_options;
int m_current_depth;
};
std::string json_encode(const variant& value, const json_encode_options& options) {
try {
encoder enc(options);
return enc.encode(value);
} catch (const mc::parse_error_exception&) {
throw;
} catch (const std::exception& e) {
MC_THROW(mc::parse_error_exception, "JSON encoding failed: ${error}", ("error", e.what()));
}
}
std::string json_encode(const dict& obj, const json_encode_options& options) {
try {
encoder enc(options);
enc.encode_object(obj);
return enc.get_result();
} catch (const mc::parse_error_exception&) {
throw;
} catch (const std::exception& e) {
MC_THROW(mc::parse_error_exception, "JSON encoding failed: ${error}", ("error", e.what()));
}
}
std::string json_encode(const std::vector<variant>& arr, const json_encode_options& options) {
try {
encoder enc(options);
enc.encode_array(arr);
return enc.get_result();
} catch (const mc::parse_error_exception&) {
throw;
} catch (const std::exception& e) {
MC_THROW(mc::parse_error_exception, "JSON encoding failed: ${error}", ("error", e.what()));
}
}
mc::variant json_decode(std::string_view json, const json_decode_options& options) {
try {
decoder dec(json, options);
return dec.decode();
} catch (const mc::parse_error_exception&) {
throw;
} catch (const std::exception& e) {
MC_THROW(mc::parse_error_exception, "JSON decoding failed: ${error}", ("error", e.what()));
}
}
json_encode_options json_encode_options::default_encode_options;
json_decode_options json_decode_options::default_decode_options;
}
}