首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >美化JSON类

问美化JSON类
EN

Code Review用户
提问于 2016-06-22 20:43:24
回答 3查看 2.4K关注 0票数 12

这并不多,但我尝试使用了一些我学到但从未真正使用过的东西,因为在我工作的地方(大部分情况下)并不真正需要这种代码。我试着尽可能多地做C++11。我想知道我是否在某个地方搞砸了,也许有些事情可以做得更好,或者你有一些提高可读性的建议?

代码语言:javascript
复制
#include <string>
#include <regex>
#include <vector>

class JSONPretify : public std::string{

public:

    JSONPretify(std::string j){
        this->assign(j);
        pretify();
    };
    JSONPretify(std::string j, bool colon_space){
        this->assign(j);
        pretify();
        if(colon_space)
            insertColonSpaces();
    };
private:
    void pretify(){
        std::regex var = std::regex(R"((\".+?\".*?(?=\{|\[|\,|\]|\}))|(\d+?))");
        long it = 0;
        int depth = 0;
        while(it < this->size() && it != -1){
            regex_pos pos_tab = findRegexFirstPosition(it, var);
            long pos_comma = this->find(",", it);
            long pos_obj_start = this->find("{", it);
            long pos_obj_end = this->find("}", it);
            long pos_array_start = this->find("[", it);
            long pos_array_end = this->find("]", it);
            long old_it = it;

            unsigned long work_with = find_lowest(std::vector<long>{pos_tab.pos, pos_comma, pos_obj_start, pos_obj_end,pos_array_start,pos_array_end});

            switch(work_with){
                case(TAB):{
                    std::string insert = generateSpaces(depth);
                    this->insert(pos_tab.pos, insert);

                    it = pos_tab.pos+insert.size()+pos_tab.length;
                    break;
                }
                case(COMMA):{
                    std::string insert = "\n";
                    this->insert(pos_comma+1, insert);

                    it = pos_comma+1;
                    break;
                }
                case(OBJ_START):{
                    std::string insert = "\n";
                    this->insert(pos_obj_start+1, insert);
                    it = pos_obj_start+insert.size();
                    depth+=1;
                    if(pos_obj_start-1 < 0 || pos_obj_start > this->size()) continue;

                    if(this->at(pos_obj_start-1) != ':'){
                        std::string extra = generateSpaces(depth-1);
                        this->insert(pos_obj_start, extra);
                        it+=extra.size();
                    }

                    break;
                }
                case(OBJ_END):{
                    std::string insert = "\n"+generateSpaces(depth-1);
                    this->insert(pos_obj_end, insert);
                    depth-=1;
                    it = pos_obj_end+insert.size()+1;
                    break;
                }
                case(ARRAY_START):{
                    depth+=1;
                    std::string insert = "\n";
                    this->insert(pos_array_start+1,insert);
                    it=pos_array_start+insert.size();
                    break;
                }
                case(ARRAY_END):{
                    depth-=1;
                    std::string insert = "\n"+generateSpaces(depth);
                    this->insert(pos_array_end,insert);
                    it=pos_array_end+insert.size()+1;
                    break;
                }
                default:{
                    break;
                }
            };


            if(it == old_it)
                break;
        }
    };

    void insertColonSpaces(){
        long pos = 0;
        while(pos < this->size() && pos != -1){
            pos = this->find(":", pos);
            if(pos == -1 || pos >= this->size()) break;
            this->replace(pos,1, " : ");

            pos+=3;
        }
    }
    struct regex_pos{
        long pos;
        long length;
    };
    std::string generateSpaces(int l){
        std::string r="";
        for(int i = 0; i < l; i++){
            r+= "    ";
        }
        return r;
    }
    regex_pos findRegexFirstPosition(long start_pos, std::regex rx){
        long at = -1;
        long l = 0;

        std::string ss(this->begin()+start_pos, this->end());
        std::smatch m;

        std::regex_search ( ss, m, rx );

        for (unsigned i=0; i<m.size(); ++i) {
            at = m.position(i);
            l = m[i].str().size();
            break;
        }
        if(at != -1) at += start_pos;
        return {at,l};
    }
    template<typename T>
    unsigned long find_lowest(std::vector<T> outof){
        unsigned long lowest_it = 0;
        for(unsigned i = 0; i < outof.size(); i++){
            if((outof[i] < outof[lowest_it] && outof[i] != -1) || (outof[lowest_it] == -1 && outof[i] != -1)){
                lowest_it = i;
            }
        }
        if(outof[lowest_it] == -1)
            lowest_it = outof.size()+1;
        return lowest_it;
    }

    enum positions{
        TAB = 0,
        COMMA = 1,
        OBJ_START = 2,
        OBJ_END = 3,
        ARRAY_START = 4,
        ARRAY_END = 5
    };
};

Github链接

EN

回答 3

Code Review用户

回答已采纳

发布于 2016-06-22 22:03:25

谢谢你编辑这个问题。我几乎没有什么想法,你可能想要完善你的代码。

std::字符串基类

JSONPretify来源于std::string,这引起了我的注意。一般说来,STL容器并不适用于这种用例。特别是,如果您是从基类派生来检查它是否有虚拟析构函数,这是一个好主意。std::string没有virtual析构函数。

见例如https://www.securecoding.cert.org/confluence/display/cplusplus/OOP52-CPP.+Do+not+delete+a+polymorphic+object+without+a+virtual+destructor

接口

我将进一步建议,由于您不需要建模任何状态,保留任何数据或不变的简单函数可能是更好的接口。

代码语言:javascript
复制
std::string pretify(const std::string& j, bool colon_space = false);

接口分离与

实现

为了能够向代码的用户隐藏所有复杂的细节,您可以将其划分为接口和实现。最常见的形式是仅迭代头文件(例如prettify.hpp)和实现源文件(例如,prettify.cpp)。然后,您可以保留prettify.cpp的所有定义和实现细节。要将其与代码的其余部分(即使是仅链接到的代码)分离开来,您可以使用匿名名称空间或内部链接函数(令人惊讶的是,这是关键字static的其他含义)。

find_lowest

我会尽量避免这个算法的实现,并使用std::string::find_first_of()和/或std::min_element()。

如果您决定坚持使用它,那么您仍然可以简化它,不让它成为一个模板,因为它只有一个调用。您可能也不希望复制参数向量,因此引用可能更合适:

代码语言:javascript
复制
unsigned long find_lowest(const std::vector<long>& outof){

work_with

我建议使用作用域枚举(伟大的C++11扩展),并在这些枚举和整数之间进行区分。

见http://en.cppreference.com/w/cpp/language/enum 作用域枚举

它所需要的只是新的positions定义:

代码语言:javascript
复制
enum class positions{

并更改值用法:

代码语言:javascript
复制
case(positions::TAB):{

我对此感到困惑

代码语言:javascript
复制
positions work_with = find_lowest(std::vector<long>{pos_tab.pos, pos_comma, pos_obj_start, pos_obj_end,pos_array_start,pos_array_end});

因为您将位置分配给work_with,但在switch中检查内容。这是正确的吗?

变量

这是一种主观观点,但省略一些辅助变量可能会增加可读性。

代码语言:javascript
复制
            case(COMMA):{
                std::string insert = "\n";
                this->insert(pos_comma+1, insert);

                it = pos_comma+1;
                break;
            }

缩短为

代码语言:javascript
复制
            case(COMMA):{
                this->insert(pos_comma+1, "\n");

                it = pos_comma+1;
                break;
            }

对于您创建的并且不打算更改的变量,我肯定会使用const让编译器知道您的意图,并让它实际检查您是否意外地违反了它。

代码语言:javascript
复制
const std::regex var = std::regex(R"((\".+?\".*?(?=\{|\[|\,|\]|\}))|(\d+?))");

const regex_pos pos_tab = findRegexFirstPosition(it, var);

const std::string insert = generateSpaces(depth);

insertColonSpaces

基本上,您正在用另一个字符串替换一个字符串。

这个问题可能会给出一些提示(例如boost::algorithm::replace_all_copy或使用std::regex)。https://stackoverflow.com/questions/5343190/how-do-i-replace-all-instances-of-a-string-with-another-string

generateSpaces

除非我忽略了一些东西,否则它和

代码语言:javascript
复制
return std::string(l * 4, ' ');

在这里检查std::string“填充”构造函数:http://www.cplusplus.com/reference/string/string/string/

(){ break;}

这个回路

代码语言:javascript
复制
    for (unsigned i=0; i<m.size(); ++i) {
        at = m.position(i);
        l = m[i].str().size();
        break;
    }

看起来更像是一个简单的条件

代码语言:javascript
复制
    if ( m.size() > 0 ) {
        at = m.position(0);
        l = m[0].str().size();
    }
票数 16
EN

Code Review用户

发布于 2016-06-23 04:03:31

继承

至少在理论上,这里使用的继承有点危险--可以隐式地将JSONPretify转换为std::string,更重要的是隐式地将JSONPretify *转换为std::string *。如果用户最终通过JSONPretify删除std::string *,则会得到未定义的行为。

因为std::string没有任何虚拟函数,所以没有覆盖任何虚拟函数。这意味着您不会通过使用继承获得更多的收益。如果您真的想使用继承,我至少会考虑使用私有继承。这样可以防止隐式转换到基类,从而消除(大部分)危险。用户仍然可以使用显式强制转换到基类,但至少是强迫他们显式地进行转换。

使用this->

虽然有些语言要求您使用this-> (或类似的语言,如self)来引用成员变量/函数,但是C++并不这样做--这样做会增加大量的视觉噪音,从而分散和损害可读性。除非真的有必要,否则它应该被避免(而这不是这里)。

变量名

这直接导致查看一些变量名,如insert。选择好的名称通常很困难,但名称并不短缺,因此我们需要为函数和变量都使用相同名称的单个语句,例如:this->insert(pos_obj_start+1, insert);。我会将变量重命名为类似于leader或prolog的内容。

考虑默认参数以减少重复,

例如,您有两个几乎相同的构造函数:

代码语言:javascript
复制
JSONPretify(std::string j){
    this->assign(j);
    pretify();
};
JSONPretify(std::string j, bool colon_space){
    this->assign(j);
    pretify();
    if(colon_space)
        insertColonSpaces();
};

我至少会考虑把这些简化成一个默认的参数:

代码语言:javascript
复制
JSONPretify(std::string j, bool colon_space = false){
    this->assign(j);
    pretify();
    if(colon_space)
        insertColonSpaces();
};

这通常会保留相同的行为--您可以将其调用为:

代码语言:javascript
复制
JSONPretify(input);
JSONPretify(input, false); // same as above
JSONPretify(input, true);

添加了一点安全

虽然这可能不是什么大不了的事,但我猜您通常不希望允许从std::string到JSONPretify的隐式转换,所以您最好将只能用一个参数调用的构造函数标记为explicit。

代码语言:javascript
复制
explicit JSONPretify(std::string j, bool colon_space = false) {
    this->assign(j);
    pretify();
    if(colon_space)
        insertColonSpaces();
};

避免布尔参数

当查看客户端代码时,我怀疑JSONPretify(foo, true)与JSONPretify(foo, false)有何不同(也就是说,布尔值控制的内容并不明显)。我会考虑使用enum来代替:

代码语言:javascript
复制
enum class colons { tight, spaced };

explicit JSONPretify(std::string j, colons spacing=colons::tight) {
    this->assign(j);
    pretify();
    if(spacing == spaced)
        insertColonSpaces();
};

这将被引用,类似于:

代码语言:javascript
复制
JSONPretify(foo, colons::spaced);

或者:

代码语言:javascript
复制
JSONPretify(foo, colons::tight);

...which看上去更清晰一些,至少对我来说是这样(尽管有些人可能更喜欢tight以外的名字)。

拼写

我甚至不愿提及这一点,但似乎这个类名应该有两个"t"s:JSONPrettify。

票数 4
EN

Code Review用户

发布于 2016-06-23 13:31:38

Nit:次要的,但函数定义不需要";“后面的"}”

代码语言:javascript
复制
     pretify();  
};

而不是开关语句作用域!

票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/132790

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档