我们有一个简单的c ++任务来解析来自http端点的大json文件,然后将值复制到自定义本地类实例。 (在实例下方的代码中obj
包含setter setField1
,setField2
等。)
此代码简单,"但json响应很大,导致一个非常大的c ++文件做同样的事情。但是,有一些事情需要考虑,即不同的类型和setter方法名称。下面是3个案例(一个int,一个bool和一个double),但我的代码包含至少50种类型。如何使代码现代化,减少错误,减少代码行?
if ( item_[i].HasMember("field1") && item_[i]["field1"].IsDouble()) {
double v = item_[i]["field1"].GetDouble();
if ( v < 0 )
throw CustomException("field1 value invalid");
obj.setField1(v);
} else {
throw CustomException("field1 missing or wrong data type");
}
if ( item_[i].HasMember("field2") && item_[i]["field2"].IsBool()) {
bool v = item_[i]["field2"].GetBool();
obj.setField2(v);
} else {
throw CustomException("field2 missing or wrong data type");
}
if ( item_[i].HasMember("field3") && item_[i]["field3"].IsInt()) {
int v = item_[i]["field3"].GetInt();
if ( v < 0 )
throw CustomException("field3 value invalid");
obj.setField3(v);
} else {
throw CustomException("field3 missing or wrong data type");
}
答案 0 :(得分:1)
我有一个具有此接口的JSON解析器
int err = 0;
JSONParser jparser(json_as_stdstring);
x = jparser.getDouble("fielda, &err);
if(err)
/* we have an error */
然而,错误是粘性的。所以代码看起来像这样
int err = 0;
JSONParser jparser(json_as_stdstring);
Myclass myclass; // object to fill;
myclass.x = jparser.getDouble("fielda", &err);
myclass.name = jparser.getString("name", &err);
myclass.id = jparser.getInteger("id" &err);
if(err)
/* we have an error */
它使复杂性远离解析,在常见情况下,JSON中的任何错误都会使整个转换无效。如果您可以容忍错误或丢失的数据,当然您可以处理它并将错误重置为0。
答案 1 :(得分:1)
这个序列化代码中最邪恶的东西是,恕我直言,重复和那些字符串标识符。在这里,我将发布我的C ++伪代码两美分(我不会使用setter,但这个想法可以很容易地扩展到使用它们)。当然,这个解决方案可能不适合你的代码,但这只是整体想法的概述。
首先,这是一个可序列化对象的声明:
class TestObject : public JsonSerializable
{
public:
TestObject()
{
// String field names are localized in a single place
// Here we create some sort of mapping from JSON to
// actual data.
addField("bool", &m_bool);
addField("int", &m_int);
addField("string", &m_string);
}
private:
bool m_bool
int m_int;
std::string m_string;
};
现在让我们定义一个JsonSerializable类来处理从JSON文件加载对象:
class JsonSerializable
{
public:
// This method iterates all registered fields
// and tries to read them from a JSON
void load(const Json& json)
{
for (const auto& kv : m_fields)
{
kv.second->set(json[kv.first]);
}
}
protected:
// This method was used in a TestObject constructor
template<typename TValue>
void addField(const std::string& name, TValue* value)
{
m_fields[name] = new GenericField(value);
}
private:
// A map to store all fields to be loaded from JSON
// (can be a list, vector or any other favourite container)
std::map<std::string, GenericField*> m_fields;
};
最后但并非至少是一个字段解析器接口:
// An interface that is exposed to JsonSerializable that hides
// a type-specific serialization process.
class Field
{
public:
// Contains just one method to set a field from a JSON value.
virtual void set(const JsonValue& value) = 0;
};
// Generic type-specific implementation
template<typename TValue>
class GenericField : public Field
{
public:
// Each field contains a pointer to a field, but here you can
// also use pointer to a method or std::function to add setters.
GenericField(TValue* value)
: m_value(value)
{
}
// And here is an actual serialization code, that extracts a
// value from a JSON and writes to a pointed chunk of memory.
virtual void set(const JsonValue& value)
{
*m_value = value.as<TValue>();
}
private:
TValue* m_value;
};
因此,这里的基本思想是通过隐藏Field接口后面的实际序列化代码并在单个位置本地化字符串标识符来消除代码重复 - 在可序列化对象的构造函数内。
希望这有帮助。
答案 2 :(得分:0)
首先,我们需要检查字段类型并根据其C ++类型获取其值的方法。
template <typename T>
bool Is(const Field &field) { return false; }
template <> bool Is<bool>(const Field &field) { return field.IsBool(); }
template <> bool Is<double>(const Field &field) { return field.IsDouble(); }
template <> bool Is<int>(const Field &field) { return field.IsInt(); }
template <typename T>
T Get(const Field &field) { throw 0; }
template <> T Get<bool>(const Field &field) { return field.GetBool(); }
template <> T Get<double>(const Field &field) { return field.GetDouble(); }
template <> T Get<int>(const Field &field) { return field.GetInt(); }
在上文中,我假设您的类型数量有限,并且为每个类型专门设置这些功能模板并不是什么大问题。请注意,Field是您在请求特定字段时JSON解析器返回的任何类型。
现在我们可以构建一个通用的提取器。我注意到你的例子也在这一步做了验证,但是我要把它分开。请注意,JSONThing是您的JSON对象的类型(与原始帖子中item_[i]
的类型相同)。
template <typename T>
T Extract(const JSONThing &json, const char * field_name) {
if (!json.HasMember(field_name)) throw CustomException("missing field")
const Field &field = json[field_name];
if (!Is<T>(field)) throw CustomException("wrong type");
return Get<T>(field);
}
对于验证,您可以使用返回输入值的函数(或函数模板),如果它有效。不可否认,这有点人为,但它使得对象非常简单。
template <typename T>
const T &Nonnegative(const T &value) {
if (value < static_cast<T>(0)) throw CustomException("invalid value");
return value;
}
现在您可以像这样填充对象:
const auto &json = item_[i];
obj.setField1(Nonnegative(Extract<double>(json, "Field1")));
obj.setField2(Extract<bool>(json, "Field2"));
obj.setField3(Nonnegative(Extract<int>(json, "Field3")));
// ...
我觉得这很可读,它几乎消除了所有重复,因此很难犯错。如果您想了解自定义例外中的更多细节,则必须多做一些工作。