Implement JSON serialization for C4Value

alut-include-path
Lukas Werling 2017-02-17 20:28:05 +01:00
parent a44bd69b2f
commit ee0b1c2599
6 changed files with 135 additions and 0 deletions

View File

@ -512,6 +512,26 @@ void C4PropList::AppendDataString(StdStrBuf * out, const char * delim, int depth
}
}
StdStrBuf C4PropList::ToJSON(int depth, bool ignore_reference_parent) const
{
if (depth <= 0 && Properties.GetSize())
{
throw new C4JSONSerializationError("maximum depth reached");
}
StdStrBuf DataString;
DataString = "{";
std::list<const C4Property *> sorted_props = Properties.GetSortedListOfElementPointers();
for (std::list<const C4Property *>::const_iterator p = sorted_props.begin(); p != sorted_props.end(); ++p)
{
if (p != sorted_props.begin()) DataString.Append(",");
DataString.Append(C4Value((*p)->Key).ToJSON());
DataString.Append(":");
DataString.Append((*p)->Value.ToJSON(depth - 1, ignore_reference_parent ? IsStatic() : nullptr));
}
DataString.Append("}");
return DataString;
}
std::vector< C4String * > C4PropList::GetSortedLocalProperties(bool add_prototype) const
{
// return property list without descending into prototype

View File

@ -141,6 +141,7 @@ public:
void CompileFunc(StdCompiler *pComp, C4ValueNumbers *);
void AppendDataString(StdStrBuf * out, const char * delim, int depth = 3, bool ignore_reference_parent = false) const;
StdStrBuf ToJSON(int depth = 10, bool ignore_reference_parent = false) const;
std::vector< C4String * > GetSortedLocalProperties(bool add_prototype=true) const;
std::vector< C4String * > GetSortedLocalProperties(const char *prefix, const C4PropList *ignore_overridden) const;
std::vector< C4String * > GetUnsortedProperties(const char *prefix, C4PropList *ignore_parent = nullptr) const;

View File

@ -184,6 +184,64 @@ StdStrBuf C4Value::GetDataString(int depth, const C4PropListStatic *ignore_refer
}
}
// JSON serialization.
// Only plain data values can be serialized. Throws a C4JSONSerializationError
// when encountering values that cannot be represented in JSON or when the
// maximum depth is reached.
StdStrBuf C4Value::ToJSON(int depth, const C4PropListStatic *ignore_reference_parent) const
{
// ouput by type info
switch (GetType())
{
case C4V_Int:
return FormatString("%ld", static_cast<long>(Data.Int));
case C4V_Bool:
return StdStrBuf(Data ? "true" : "false");
case C4V_PropList:
{
const C4PropListStatic * Def = Data.PropList->IsStatic();
if (Def)
if (!ignore_reference_parent || Def->GetParent() != ignore_reference_parent)
return Def->ToJSON();
return Data.PropList->ToJSON(depth, Def && ignore_reference_parent);
}
case C4V_String:
if (Data.Str && Data.Str->GetCStr())
{
StdStrBuf str = Data.Str->GetData();
str.EscapeString();
str.Replace("\n", "\\n");
return FormatString("\"%s\"", str.getData());
}
else
{
return StdStrBuf("null");
}
case C4V_Array:
{
if (depth <= 0 && Data.Array->GetSize())
{
throw C4JSONSerializationError("maximum depth reached");
}
StdStrBuf DataString;
DataString = "[";
for (int32_t i = 0; i < Data.Array->GetSize(); i++)
{
if (i) DataString.Append(",");
DataString.Append(std::move(Data.Array->GetItem(i).GetDataString(depth - 1)));
}
DataString.AppendChar(']');
return DataString;
}
case C4V_Function:
throw C4JSONSerializationError("cannot serialize function");
case C4V_Nil:
return StdStrBuf("null");
default:
throw C4JSONSerializationError("unknown type");
}
}
const C4Value & C4ValueNumbers::GetValue(uint32_t n)
{
if (n <= LoadedValues.size())

View File

@ -61,6 +61,14 @@ union C4V_Data
C4V_Data &operator = (void *p) { assert(!p); Ptr = p; return *this; }
};
class C4JSONSerializationError : public std::exception
{
std::string msg;
public:
C4JSONSerializationError(const std::string& msg) : msg(msg) {}
virtual const char* what() const noexcept override { return msg.c_str(); }
};
class C4Value
{
public:
@ -158,6 +166,7 @@ public:
void Denumerate(C4ValueNumbers *);
StdStrBuf GetDataString(int depth = 10, const class C4PropListStatic *ignore_reference_parent = nullptr) const;
StdStrBuf ToJSON(int depth = 10, const class C4PropListStatic *ignore_reference_parent = nullptr) const;
ALWAYS_INLINE bool CheckParConversion(C4V_Type vtToType) const // convert to dest type
{

View File

@ -34,3 +34,41 @@ TEST(C4ValueTest, SanityTests)
EXPECT_TRUE(C4Value(true));
EXPECT_FALSE(C4Value(false));
}
TEST(C4ValueTest, ToJSON)
{
// Wrapping in std::string makes GTest print something useful in case of failure.
#define EXPECT_STDSTRBUF_EQ(a, b) EXPECT_EQ(std::string((a).getData()), std::string(b));
// simple values
EXPECT_STDSTRBUF_EQ(C4Value(42).ToJSON(), "42");
EXPECT_STDSTRBUF_EQ(C4Value(-42).ToJSON(), "-42");
EXPECT_STDSTRBUF_EQ(C4Value("foobar").ToJSON(), R"#("foobar")#");
EXPECT_STDSTRBUF_EQ(C4Value("es\"caping").ToJSON(), R"#("es\"caping")#");
EXPECT_STDSTRBUF_EQ(C4Value("es\\caping").ToJSON(), R"#("es\\caping")#");
EXPECT_STDSTRBUF_EQ(C4Value("new\nline").ToJSON(), R"#("new\nline")#");
EXPECT_STDSTRBUF_EQ(C4Value(true).ToJSON(), R"#(true)#");
EXPECT_STDSTRBUF_EQ(C4Value(false).ToJSON(), R"#(false)#");
EXPECT_STDSTRBUF_EQ(C4Value().ToJSON(), R"#(null)#");
// proplists
auto proplist = C4PropList::NewStatic(nullptr, nullptr, nullptr);
proplist->SetProperty(P_Options, C4Value("options"));
proplist->SetProperty(P_Min, C4Value(13));
auto nested = C4PropList::NewStatic(nullptr, nullptr, nullptr);
nested->SetProperty(P_Description, C4Value(true));
proplist->SetProperty(P_Storage, C4Value(nested));
EXPECT_STDSTRBUF_EQ(C4Value(proplist).ToJSON(), R"#({"Min":13,"Options":"options","Storage":{"Description":true}})#");
auto crazy_key = C4PropList::NewStatic(nullptr, nullptr, nullptr);
auto key = Strings.RegString("foo\"bar");
proplist->SetPropertyByS(key, C4Value(42));
EXPECT_STDSTRBUF_EQ(C4Value(proplist).ToJSON(), R"#({"foo\"bar":42})#");
// arrays
auto array = new C4ValueArray(3);
array->SetItem(0, C4Value(1));
array->SetItem(1, C4Value(2));
array->SetItem(2, C4Value(3));
EXPECT_STDSTRBUF_EQ(C4Value(array).ToJSON(), R"#([1,2,3])#");
}

View File

@ -130,6 +130,15 @@ if (GTEST_FOUND AND GMOCK_FOUND)
LIBRARIES
libmisc
libc4script)
# This is included in "tests" (above) as well, but that executable currently doesn't compile.
create_test(c4value_test
SOURCES
C4ValueTest.cpp
../src/script/C4ScriptStandaloneStubs.cpp
../src/script/C4ScriptStandalone.cpp
LIBRARIES
libmisc
libc4script)
else()
set(_gtest_missing "")
if (NOT GTEST_INCLUDE_DIR)