使用C 技术更少的代码

Less code using c++ techniques

本文关键字:代码 技术 使用      更新时间:2023-10-16

我们有一个琐碎的C 任务,可以从HTTP端点解析一个大JSON文件,然后将值复制到自定义的本地类实例。(在下面的代码中,该实例是 obj,带有seters setField1setField2等)

此代码很"简单",但是JSON响应很大,并且导致一个非常大的C 文件可以执行相同的操作。但是,有一些要考虑的事情,即不同的类型和二传方法名称。以下是3个案例(一个int,一个布尔和双重),但我的代码包含至少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");
}

此序列化代码中最邪恶的事情是,恕我直言,复制和那些字符串标识符。在这里,我将张贴我的C 伪代码两美分(我不会使用Setters,但是可以轻松地扩展此想法以使用它们)。当然,此解决方案可能不符合您的代码,但这只是整体想法的概述。

首先,这是一些可序列化对象的声明:

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;
};

现在,让我们定义一个可从JSON文件处理对象的可添加jsonserializizizizable类:

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;
};

因此,这里的基本思想是通过隐藏字段接口后面的实际序列化代码来消除代码重复,并将字符串标识符定位在一个位置 - 在一个可序列化对象的构造函数中。

希望这会有所帮助。

我有一个具有此接口的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中的任何错误都会使整个转换无效。如果您可以忍受不良数据或缺少数据,那么您当然可以处理并将ERR重置为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(); }

在上面,我假设您的类型有限,并且为每个功能模板专门为每个功能模板提供了很大的意义。请注意,当您请求特定字段时,该字段是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")));
// ...

我认为这很可读,它几乎消除了所有重复,因此几乎没有机会犯错。如果您需要在自定义异常中进行更多详细信息,则必须做更多的工作。