Skip to content

Commit

Permalink
Add JsonDumper
Browse files Browse the repository at this point in the history
  • Loading branch information
vvromanov committed Jan 4, 2025
1 parent 2adb0c7 commit 313e682
Show file tree
Hide file tree
Showing 4 changed files with 327 additions and 0 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ add_library(${TARGET} STATIC
src/common_utils.cpp
src/DumpUtils.cpp
src/DumpUtils.h
src/JsonDumper.h
src/file_utils.h
src/file_utils.cpp
src/Status.cpp
Expand Down
247 changes: 247 additions & 0 deletions src/JsonDumper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
#pragma once
#include <cmath>
#include <cstdint>
#include <format>
#include <functional>
#include <ostream>
#include <stack>

const char* GetIndentStr(uint32_t indent);

class JsonDumper {
std::ostream& s;
int indent { 0 };
bool member_started { false };
struct state_s {
bool in_array { false };
bool first_element { true };
bool wrap { false };
} state_s;
std::stack<struct state_s> state_storage;
struct state_s state;

public:
JsonDumper(std::ostream& _s)
: s { _s }
{
}
void SetWrap(bool _wrap = true) { state.wrap = _wrap; }

void NewLine()
{
if (!state.first_element && state.in_array) {
s << ",";
}
if (!state.wrap) {
s << std::endl;
}
state.first_element = true;
}

JsonDumper& StartObject(bool is_array = false)
{
NextItem();
Push(is_array);
s << (is_array ? '[' : '{');
if (state.wrap) {
indent += 2;
}
member_started = false;
return *this;
}
JsonDumper& StartArray()
{
return StartObject(true);
}

JsonDumper& StartMember(const char* name)
{
if (state.in_array) {
throw new std::bad_function_call();
}
if (!state.first_element) {
s << ',';
if (!state.wrap) {
s << ' ';
}
} else {
state.first_element = false;
}
if (state.wrap) {
s << std::endl;
s << GetIndentStr(indent);
}
s << "\"" << name << "\": ";
member_started = true;
return *this;
}
JsonDumper& End()
{
if (state.wrap) {
if (!state.first_element) {
s << std::endl;
s << GetIndentStr(indent - 2);
}
}
s << (state.in_array ? "]" : "}");
Pop();
if (state.wrap) {
indent -= 2;
}
member_started = false;
return *this;
}

JsonDumper& Write(const char* str, size_t max_len)
{
for (size_t i = 0; i < max_len; i++) {
if (str[i] == '\0') {
return *this << std::string_view(str, i);
}
}
return *this << std::string_view(str, max_len);
}

JsonDumper& operator<<(const char* str)
{
return *this << std::string_view(str);
}

JsonDumper& operator<<(const std::string& str)
{
return *this << std::string_view(str);
}

JsonDumper& operator<<(double v)
{
if (std::isnan(v)) {
return *this << "nan";
}
if (state.in_array) {
NextItem();
}
s << v;
member_started = false;
return *this;
}

JsonDumper& operator<<(float v)
{
if (std::isnan(v)) {
return *this << "nan";
}
return *this << (double)v;
}

JsonDumper& operator<<(int64_t v)
{
if (state.in_array) {
NextItem();
}
s << v;
member_started = false;
return *this;
}

JsonDumper& operator<<(uint64_t v)
{
if (state.in_array) {
NextItem();
}
s << v;
member_started = false;
return *this;
}
JsonDumper& operator<<(int32_t v) { return *this << (int64_t)v; }
JsonDumper& operator<<(uint32_t v) { return *this << (uint64_t)v; }
JsonDumper& operator<<(int16_t v) { return *this << (int64_t)v; }
JsonDumper& operator<<(uint16_t v) { return *this << (uint64_t)v; }
JsonDumper& operator<<(int8_t v) { return *this << (int64_t)v; }
JsonDumper& operator<<(uint8_t v) { return *this << (uint64_t)v; }

template <class T>
JsonDumper& operator<<(T v)
{
if (state.in_array) {
NextItem();
}
s << '"' << v << '"';
member_started = false;
return *this;
}

JsonDumper& operator<<(std::string_view sv)
{
if (state.in_array) {
NextItem();
}
s << '"';
for (auto c : sv) {
if ((c == '"') || (c == '\\') || (('\0' < c) && (c < ' '))) {
switch (c) {
case '"':
s << "\\\"";
break;
case '\\':
s << "\\\\";
break;
case '\b':
s << "\\b";
break;
case '\f':
s << "\\f";
break;
case '\n':
s << "\\n";
break;
case '\r':
s << "\\r";
break;
case '\t':
s << "\\t";
break;
default:
s << std::format("\\u{:04x}", static_cast<uint16_t>(c));
break;
}
} else {
s << c;
}
}
s << '"';
member_started = false;
return *this;
}

protected:
void Push(bool _in_array)
{
state_storage.push(state);
state.first_element = true;
state.in_array = _in_array;
}
void Pop()
{
state = state_storage.top();
state_storage.pop();
}
void NextItem()
{
if (!state.first_element && state.in_array) {
if (state_storage.size() > 1) {
s << ',';
} else {
s << ", ";
}
}
if (state.wrap) {
if (!member_started) {
if (state_storage.size() > 0 || !state.first_element) {
s << std::endl;
}
s << GetIndentStr(indent);
}
}
state.first_element = false;
}
};
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ add_executable(${TARGET}
TestTimeUtils.cpp
TestUptime.cpp
ClockOverride.h
JsonDumperTest.cpp
)

target_link_libraries(${TARGET}
Expand Down
78 changes: 78 additions & 0 deletions tests/JsonDumperTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#include "JsonDumper.h"
#include "gtest/gtest.h"

TEST(JsonDumper, EmptyObject)
{
std::ostringstream ss;
JsonDumper j(ss);
j.StartObject();
j.End();
EXPECT_EQ("{}", ss.str());
}

TEST(JsonDumper, EmptyArray)
{
std::ostringstream ss;
JsonDumper j(ss);
j.StartArray();
j.End();
EXPECT_EQ("[]", ss.str());
}

static void DumpTest(JsonDumper& j)
{
j.StartObject();
j.StartMember("Test").StartObject();
j.StartMember("Int8") << (int8_t)-50;
j.StartMember("Int16") << (int16_t)-10;
j.StartMember("Int32") << (int32_t)-10;
j.StartMember("Int64") << (int64_t)-10;
j.StartMember("UInt8") << (uint8_t)70;
j.StartMember("UInt16") << (uint16_t)10;
j.StartMember("UInt32") << (uint32_t)10;
j.StartMember("UInt64") << (uint64_t)10;
j.StartMember("Char") << (char)'c';
j.StartMember("c_str") << "c_string";
j.StartMember("std_str") << std::string("std::string");
j.StartMember("string_view") << std::string_view("sv");
j.StartMember("arr").StartArray();
for (int i = 0; i < 5; i++) {
j << i;
}
j.End();
j.End();
j.End();
}

TEST(JsonDumper, Object)
{
std::ostringstream ss;
JsonDumper j(ss);
j.SetWrap(true);
DumpTest(j);
EXPECT_EQ(
"{\n"
" \"Test\": {\n"
" \"Int8\": -50,\n"
" \"Int16\": -10,\n"
" \"Int32\": -10,\n"
" \"Int64\": -10,\n"
" \"UInt8\": 70,\n"
" \"UInt16\": 10,\n"
" \"UInt32\": 10,\n"
" \"UInt64\": 10,\n"
" \"Char\": \"c\",\n"
" \"c_str\": \"c_string\",\n"
" \"std_str\": \"std::string\",\n"
" \"string_view\": \"sv\",\n"
" \"arr\": [\n"
" 0,\n"
" 1,\n"
" 2,\n"
" 3,\n"
" 4\n"
" ]\n"
" }\n"
"}",
ss.str());
}

0 comments on commit 313e682

Please sign in to comment.