forked from Mirrors/openclonk
Implement JSON serialization for C4Value
parent
a44bd69b2f
commit
ee0b1c2599
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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])#");
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue