C++ 中的多态 JSON 序列化






4.60/5 (5投票s)
使用 JSON.h (版本 0.3,支持多态类型和 std::shared_ptr)
引言
本文档介绍了 JSON 的一种实现,该实现允许序列化和反序列化从接口类型派生的多态 C++ 对象。 以前我写过关于如何使用一个简短的存根对象来描述可序列化 C++ 对象公开的属性。 在本文中,我将扩展我之前描述的工作,这次描述如何序列化抽象基类,前提是预先注册了从基类派生的类型。 你可以在这里找到本文中使用的代码,网址是 github: https://github.com/PhillipVoyle/json_h
如果你有兴趣,也可以查看我之前的文章,在这里我的 文章页面:
或者在这里查看我的博客:https://dabblingseriously.wordpress.com/
Using the Code
你将要做的第一件事是定义将作为你的基类的接口。 在这个例子中,我将使用一个简短的计算器示例,它有一个虚拟方法 Execute,然后我将从那个抽象基类派生三个子类:一个 Add 操作,Multiply,然后是一个终端操作 Value,它返回一个值。
#include "json/JSON.h"
class IOperation
{
public:
virtual float Execute() = 0;
};
class Add : public IOperation
{
public:
std::shared_ptr<IOperation> operand1;
std::shared_ptr<IOperation> operand2;
Add()
{
}
Add(std::shared_ptr<IOperation> o1,
std::shared_ptr<IOperation> o2)
:operand1(o1), operand2(o2)
{
}
float Execute()
{
return operand1->Execute() + operand2->Execute();
}
};
class Multiply : public IOperation
{
public:
std::shared_ptr<IOperation> operand1;
std::shared_ptr<IOperation> operand2;
Multiply()
{
}
Multiply(std::shared_ptr<IOperation> o1,
std::shared_ptr<IOperation> o2)
:operand1(o1), operand2(o2)
{
}
float Execute()
{
return operand1->Execute() * operand2->Execute();
}
};
class Value: public IOperation
{
public:
float value;
Value()
{
value = 0;
}
Value(float v): value(v)
{
}
float Execute()
{
return value;
}
};
其次,你将需要使用基于 JSON.h 预处理器宏的存根将类暴露给 JSON.h。 如你所见,接口描述符与类描述符非常相似。
BEGIN_CLASS_DESCRIPTOR(Value)
CLASS_DESCRIPTOR_ENTRY(value)
END_CLASS_DESCRIPTOR();
BEGIN_CLASS_DESCRIPTOR(Add)
CLASS_DESCRIPTOR_ENTRY(operand1)
CLASS_DESCRIPTOR_ENTRY(operand2)
END_CLASS_DESCRIPTOR()
BEGIN_CLASS_DESCRIPTOR(Multiply)
CLASS_DESCRIPTOR_ENTRY(operand1)
CLASS_DESCRIPTOR_ENTRY(operand2)
END_CLASS_DESCRIPTOR()
BEGIN_INTERFACE_DESCRIPTOR(IOperation)
INTERFACE_DESCRIPTOR_ENTRY(Value)
INTERFACE_DESCRIPTOR_ENTRY(Add)
INTERFACE_DESCRIPTOR_ENTRY(Multiply)
END_INTERFACE_DESCRIPTOR()
最后,你可以使用读取器和写入器机制来加载或存储对象
void RWInterface()
{
std::string sJSON = "{"
"\"type\":\"Add\","
"\"operand1\":{\"type\":\"Multiply\","
"\"operand1\":{\"type\":\"Value\",\"value\":10},"
"\"operand2\":{\"type\":\"Value\",\"value\":15}}"
",\"operand2\":{\"type\":\"Value\",\"value\":26}}";
std::shared_ptr<IOperation> test;
FromJSON(test, sJSON);
std::string sOut = ToJSON(test);
std::cout << sOut << std::endl;
auto result = test->Execute();
std::cout << "test->Execute() = " << result << std::endl;
if((sOut == sJSON) && (result == 176))
{
std::cout << "test pass" << std::endl;
}
else
{
std::cout << "test fail" << std::endl;
}
}
真简单! 作为参考,这是输出
{"type":"Add","operand1":{"type":"Multiply","operand1":{"type":"Value","value":10},"operand2":{"type":"Value","value":15}},"operand2":{"type":"Value","value":26}}
test->Execute() = 176
test pass
工作原理
这就是你需要了解的关于如何使用 JSON.h 序列化和反序列化 C++ 中的多态类型的一切,但如果你有兴趣,我可以向你展示一些管道。 首先,我将向你展示 PointerTypeDescriptor.h 的文本,基本上,这个文件实现用于 std::shared_ptr 的读取器和写入器。 这对于序列化多态对象是不够的,为此,我们还需要接口,我将在接下来展示给你看
#ifndef cppreflect_PointerTypeDescriptor_h
#define cppreflect_PointerTypeDescriptor_h
#include <memory>
template<typename T, typename TReader>
std::shared_ptr<T> CreateAndRead(
const ClassDescriptor<T>& descriptor,
TReader& reader)
{
auto result = std::make_shared<T>();
ReadObject(reader, *result);
return result;
}
template<typename T>
class ClassDescriptor;
template<typename T>
class PointerTypeDescriptor
{
};
template<typename T>
class ClassDescriptor<std::shared_ptr<T>>
{
public:
typedef PointerTypeDescriptor<std::shared_ptr<T>> descriptor_t;
};
template<typename TReader, typename T>
void DispatchReadObject(
const PointerTypeDescriptor<std::shared_ptr<T>> & descriptor,
TReader &reader,
std::shared_ptr<T>& t)
{
typename ClassDescriptor<T>::descriptor_t desc;
t = CreateAndRead(desc, reader);
}
template<typename TWriter, typename T>
void DispatchWriteObject(
const PointerTypeDescriptor<std::shared_ptr<T>> & descriptor,
TWriter &writer,
const std::shared_ptr<T>& t)
{
if(t == nullptr)
{
writer.WriteNull();
}
else
{
WriteObject(writer, *t.get());
}
}
#endif
你将在这里看到一个名为 CreateAndRead 的新泛型,它接受一个 ClassDescriptor,我们将为它提供一个重写,它接受一个新的类型 InterfaceDescriptor。 下面是 InterfaceDescriptor.h 的正文。 该文件包含前面提到的描述符类型,一些新的预处理器宏,你将在上面的示例中使用过,以及一些用于读取和写入多态类型的方法。 请注意 dynamic_cast 的使用。 这是一种在高层语言中常用的操作,但在 C++ 中并不常用,因为它有开销。 随时给我写信并提出另一种机制 - 我只用它来确定我正在编写的对象的类型。 还要注意 CreateAndRead 的重写。
#ifndef cppreflect_InterfaceDescriptor_h
#define cppreflect_InterfaceDescriptor_h
template<typename T>
class InterfaceDescriptor
{
};
#define BEGIN_INTERFACE_DESCRIPTOR(X) \
template<> class InterfaceDescriptor<X>; \
template<> class ClassDescriptor<X> { public: typedef InterfaceDescriptor<X> descriptor_t;}; \
template<> \
class InterfaceDescriptor<X> { public: \
template<typename TCallback>\
void for_each_interface(TCallback callback) const\
{
#define INTERFACE_DESCRIPTOR_ENTRY(X) callback(ClassDescriptor<X>::descriptor_t{});
#define END_INTERFACE_DESCRIPTOR() \
} \
};
template<typename TInterface, typename TReader>
class TryReadObjectFunctor
{
std::shared_ptr<TInterface>& m_t;
TReader &m_reader;
std::string m_type;
public:
TryReadObjectFunctor(std::shared_ptr<TInterface>& t, TReader &reader, const std::string& type):m_t(t), m_reader(reader), m_type(type)
{
}
template<typename TConcrete>
void operator()(ClassDescriptor<TConcrete> descriptor) const
{
if(m_type != descriptor.get_name())
{
return;
}
auto t = std::make_shared<TConcrete>();
m_t = t;
std::string sProperty;
while(!m_reader.IsEndObject())
{
m_reader.NextProperty(sProperty);
ReadObjectFunctor<TReader, TConcrete> functor {m_reader, *t, sProperty};
descriptor.for_each_property(functor);
if(!functor.m_bFound)
{
throw std::runtime_error("could not find property");
}
}
}
};
template<typename TReader, typename T>
std::shared_ptr<T> CreateAndRead(const InterfaceDescriptor<T> & descriptor, TReader &reader)
{
std::shared_ptr<T> result;
reader.EnterObject();
if(!reader.IsEndObject())
{
std::string sProperty;
reader.FirstProperty(sProperty);
if(sProperty != "type")
{
throw std::runtime_error("expected type property");
}
std::string sType;
ReadObject(reader, sType);
TryReadObjectFunctor<T, TReader> functor {result, reader, sType};
descriptor.for_each_interface(functor);
}
reader.LeaveObject();
return result;
}
template<typename TInterface, typename TWriter>
class TryWriteObjectFunctor
{
const TInterface& m_t;
TWriter &m_writer;
public:
TryWriteObjectFunctor(const TInterface& t, TWriter &writer):m_t(t), m_writer(writer)
{
}
template<typename TConcrete>
void operator()(ClassDescriptor<TConcrete> descriptor) const
{
try {
const TConcrete& concrete = dynamic_cast<const TConcrete&>(m_t); //todo: prefer faster mechanism?
m_writer.BeginObject(descriptor.get_name());
m_writer.BeginProperty("type");
WriteObjectFunctor<TWriter, TConcrete> functor(m_writer, concrete, false);
WriteObject(m_writer, std::string(descriptor.get_name())); //fixme: (allow const char*)
m_writer.EndProperty();
descriptor.for_each_property(functor);
m_writer.EndObject();
}
catch (std::bad_cast e)
{
//ignore it
}
}
};
template<typename TWriter, typename T>
void DispatchWriteObject(const InterfaceDescriptor<T> & descriptor, TWriter &writer, const T& t)
{
TryWriteObjectFunctor<T, TWriter> functor {t, writer};
descriptor.for_each_interface(functor);
}
#endif
总结
好的,现在你可以使用 C++ 编写 JSON 对象了,这很有趣,但可能有点冗长。 这种事情仍然需要相当大的努力,但这将是至少在下一个阶段(十年?)标准委员会就反射标准达成一致意见的方式。
在我的下一篇博客中,我希望使用其中的一些东西来使用 boost.asio 在控制台应用程序中托管 JSON Web 服务。 如果你仍然有兴趣,请来参观。
感谢阅读。 如果你喜欢这篇文章或我的代码,或者有任何其他评论,我很乐意收到你的来信,所以请给我留言,或者评论这篇文章。
这篇文章最初发布在这里:https://dabblingseriously.wordpress.com/2015/06/30/polymorphic-json-serialization-in-c/