首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >改进C++的JSON解析器

改进C++的JSON解析器
EN

Code Review用户
提问于 2019-11-24 22:50:29
回答 2查看 889关注 0票数 7

我为C++编写了一个JSON解析器。它不是特别快,有效率或优雅。我想要改变这一点,首先我希望代码更优雅。我如何改进它,并消除所有的代码气味?

旁白:我一生中从未上过计算机科学课程,我不知道这与我试图实现的.i.e ( LL1解析器)的理论模型有多大的偏差。也许这就是为什么它感觉如此无趣。

头文件如下所示。

代码语言:javascript
运行
复制
#pragma once

#include <string>
#include <vector>
#include <map>
#include <variant>
#include <algorithm>
#include <fstream>
#include <stack>

// Debugging
#include <iostream>

// Types to store JSON ouput

struct jlist;
struct jobject;

using json_value = std::variant<int, float, bool, std::string, jlist, jobject>;

struct jlist {
    std::vector<json_value> vector_value;

    json_value & operator [](int index) {
        return vector_value[index];
    }

    void push_back(json_value & value) {
        vector_value.push_back(value);
    }

};

struct jobject {
    std::map<std::string, json_value> map_value;

    json_value & operator [](std::string key) {
        return map_value[key];
    }

    void insert(json_value & key, json_value & value) {
        map_value.insert( { std::get<std::string>(key), value } );
    }

};

class JSONParser
{
public:
        JSONParser();

        ~JSONParser();

        void parseFile(std::string);

private:
        json_value root;
        std::stack<std::string> s;
        std::stack<json_value> s_value;

        // Lexer
        bool checkDeliminator(char);
        std::vector<std::string> lexer(std::ifstream &);

        // FSM varaibles
        enum state { int_value, float_value, bool_value, string_value, default_value, bad_state};
        state current;

        // FSM
        void fsm(std::string);

        // Parser variables
        enum stack_map { list_open, list_close, object_open, object_close, colon, comma, buffer, follow};
        std::map<std::string, stack_map> stack_conversion;

        // Parser helper functions
        template<typename T> void addElement();

        template<typename T> void insert(std::string &, T (*)(const std::string &));
        template<typename T> void insert();
        void insert(std::string &);
        void pushBuffer();

        template<typename ... T> bool multiComparision(const char scope, T ... args);
        bool isDigit(const char);
        static int st2i(const std::string & value);
        static float st2f(const std::string & value);
        static bool st2b(const std::string & value);

        // Parser
        void parser(const std::string & cursor);
};

执行情况如下

代码语言:javascript
运行
复制
#include "JSONParser.h"

JSONParser::JSONParser() {
    state current = default_value;
    stack_conversion = { { "[", list_open }, { "]", list_close }, { "{", object_open }, { "}", object_close }, { ":", colon }, { ",", comma }, { "buffer", buffer } };
}

JSONParser::~JSONParser() = default;

void JSONParser::parseFile(std::string FILE) {
    std::ifstream configfile(FILE);
    std::vector<std::string> scan = lexer(configfile);

    scan.push_back("terminate");
    for (auto it = scan.begin(); it != scan.end(); ++it) {
            parser(*it);
    }
    root = s_value.top();
    s_value.pop();
}

// Lexer
bool JSONParser::checkDeliminator(char piece) {
    switch (piece) {
        case '[':
            return true;
        case ']':
            return true;
        case '{':
            return true;
        case '}':
            return true;
        case ':':
            return true;
        case ',':
            return true;
        default:
            return false;
    }
}

std::vector<std::string> JSONParser::lexer(std::ifstream & configfile) {
    char piece;
    std::string capture = "";
    std::string conversion;
    std::vector<std::string> capture_list;

    while(configfile >> piece) {
        if (checkDeliminator(piece)) {
            conversion = piece;
            if (capture != "") {
                capture_list.push_back(capture);
                capture_list.push_back(conversion);
                capture = "";
            } else {
                capture_list.push_back(conversion);
            }
        } else {
            capture += piece;
        }
    }

    return capture_list;
}

// FSM
void JSONParser::fsm(std::string value) {
    current = default_value;
    char point;
    auto it = value.begin();

    while (it != value.end()) {
        point = *it;
        if (point == '"' & current == default_value) {
            current = string_value;
            return;
        } else if (isdigit(point)) {
            if (current == default_value | current == int_value) {
                current = int_value;
                ++it;
            } else if (current == float_value) {
                ++it;
            } else {
                current = bad_state;
                return;
            }
        } else if (point == '.' & current == int_value) {
            current = float_value;
            ++it;
        } else if (point == 'f' & current == float_value) {
            ++it;
        } else if (current == default_value) {
            if (value == "true" | value == "false") {
                current = bool_value;
                return;
            } else {
                current = bad_state;
                return;
            }
        } else {
            current = bad_state;
            return;
        }
    }
}

// Parser Helper functions
template<>
void JSONParser::addElement<jobject>() {
    json_value value_read;
    json_value key_read;

    value_read = s_value.top();
    s_value.pop();
    key_read = s_value.top();
    s_value.pop();

    std::get<jobject>(s_value.top()).insert(key_read, value_read);
}

template<>
void JSONParser::addElement<jlist>() {
    json_value value_read;

    value_read = s_value.top();
    s_value.pop();

    std::get<jlist>(s_value.top()).push_back(value_read);
}

template<typename T>
void JSONParser::insert(std::string & value, T (*fptr)(const std::string &)) {
        T T_value(fptr(value));
        s_value.push(T_value);
}

template<typename T>
void JSONParser::insert() {
        T T_value;
        s_value.push(T_value);
}

void JSONParser::insert(std::string & value) {
    value.erase(std::remove(value.begin(), value.end(), '"'), value.end());
        s_value.push(value);
}

void JSONParser::pushBuffer() {
    s.pop();
    s.push("buffer");
}

template<typename ... T>
bool JSONParser::multiComparision(const char scope, T ... args) {
    return (scope == (args || ...));
}

bool JSONParser::isDigit(const char c) {
    return multiComparision<char>(c, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0');
}

int JSONParser::st2i(const std::string & value) {
        return stoi(value);
}

float JSONParser::st2f(const std::string & value) {
        return stof(value);
}

bool JSONParser::st2b(const std::string & value) {
        if (value == "true") {
                return true;
        } else {
                return false;
        }
}

// Parser
void JSONParser::parser(const std::string & cursor) {
    if(s.empty()) {
        s.push(cursor); 
    } else {
        stack_map stack_value;
        std::string value = s.top();

        if (stack_conversion.find(value) != stack_conversion.end()) {
            stack_value = stack_conversion[s.top()];
        } else {
            stack_value = follow;
        }

        switch (stack_value) {
            case buffer:
                s.pop();
                break;
            case list_open:
                insert<jlist>();
                if (cursor == "]") {
                    pushBuffer();
                    return;
                }
                break;
            case list_close:
                addElement<jlist>();
                s.pop();
                s.pop();
                break;
            case object_open:
                insert<jobject>();
                if (cursor == "}") {
                    pushBuffer();
                    return;
                }
                break;
            case object_close:
                addElement<jobject>();
                s.pop();
                s.pop();
                break;
            case colon:
                s.pop();
                break;
            case comma:
                s.pop();
                if (s.top() == "{") {
                    addElement<jobject>();
                } else {
                    addElement<jlist>();
                }
                break;
            default:
                s.pop();
                fsm(value);
                switch (current) {
                    case string_value:
                        insert(value);
                        break;
                    case int_value:
                        insert<int>(value, st2i);
                        break;
                    case float_value:
                        insert<float>(value, st2f);
                        break;
                    case bool_value:
                        insert<bool>(value, st2b);
                        break;
                    default:
                        std::cout << "Bad state\n"; 
                }
        }
        s.push(cursor);
    }
}

这样做的目的是让lexer在每个分隔符处中断,并将生成的所有令牌放入一个向量中。然后,这个名为scan的载体可以被循环通过。在这个循环的每一次迭代中,都会运行parser。通常,这会读取堆栈s的顶部,并确定方括号/大括号是打开还是关闭,还是达到了终端值。如果一个括号/大括号正在打开,则生成一个新的jobjectjlist,并将其放置到新的堆栈s_value中;如果到达终端值,则运行fsm (有限状态机),并确定值的类型并将其放在s_value之上,如果到达逗号或结束括号,则从堆栈中移出适当的值,并将来自s_value的元素插入到相应的容器中。

这个意大利面中最大的肉丸是如何调用JSON树中的元素。

代码语言:javascript
运行
复制
std::cout << std::get<bool>(std::get<jobject>(std::get<jobject>(std::get<jlist>(root)[6])["input"])["bool"]); 

嵌套的std::get调用似乎完全错误,我不确定它们是否可以合并到operator []中。

为了完整起见,这是解析的JSON文件,因此上面的调用将输出1。

代码语言:javascript
运行
复制
[
{
    "libraries":[
        "terminal",
        "binary"
    ]
,
    "functions":[
        "terminal-basic",
        "binary-basic"
    ]
}
,
{
    "name":"addition",
    "type":"binary-basic",
    "function":"add_float",
    "input":{
        "float" : 2.0f
        },
    "output":"float",
    "max-number":2
}
,
{
    "name":"exponent",
    "type":"binary-basic",
    "function":"exponent_float",
    "input":{
        "float":2.0f
        },
    "output":"float",
    "max-number":2
}
,
{
    "name":"exponent",
    "type":"binary-basic",
    "function":"exponent_float",
    "input":{
        "float":2.0f,
        "int":1
        },
    "output":"float",
    "max-number":1
}
,
{
    "name":"constant_1",
    "type":"terminal-basic",
    "function":"non_random_constant",
    "value":0.5f,
    "input":{ },
    "output":"float",
    "max-number":3
}
,
{
    "name":"constant_2",
    "type":"terminal-basic",
    "function":"non_random_constant",
    "value":2.0f,
    "input":{ },
    "output":"float",
    "max-number":3
}
,
{
    "name":"constant_3",
    "type":"terminal-basic",
    "function":"non_random_constant",
    "value":true,
    "input":{
        "bool":true
    },
    "output":"bool",
    "max-number":1
}
]

我怎样才能改进我所拥有的?

EN

回答 2

Code Review用户

回答已采纳

发布于 2019-11-25 15:02:12

概述

我的主要问题是首先将整个流转换为令牌。然后解析令牌。这个可能会很贵。通常,您将解析(并推送)足够多的标记来理解要解释的下一部分。然后弹出你可以转换的下一部分。

我不喜欢有两种不同类型的令牌状态的方式:

代码语言:javascript
运行
复制
    stack_conversion = { { "[", list_open }, { "]", list_close }, { "{", object_open }, { "}", object_close }, { ":", colon }, { ",", comma }, { "buffer", buffer } };

    ....
    enum state { int_value, float_value, bool_value, string_value, default_value, bad_state};

我将有一个所有标记的列表(JSON中没有那么多标记)。

代码语言:javascript
运行
复制
    {, }, [, ], :, ',', null, true, false, number(int), number(float), string

使用lex和yacc (或者实际上有更多的现代对等物)、flex和bison可能是更好的编写方法。为了实现这一点,已经对这些工具进行了大量的研究,您可以在大约20行代码中指定一个json解析器。

这不像你想的那样。

代码语言:javascript
运行
复制
template<typename ... T>
bool JSONParser::multiComparision(const char scope, T ... args) {
    return (scope == (args || ...));
}

这就扩大了

代码语言:javascript
运行
复制
JSONParser::multiComparision('a', '1', '2', '3');

=>
   return ('a' == ('1' || '2' || '3'));

我不认为这是你想要的。

票数 4
EN

Code Review用户

发布于 2019-11-25 19:15:28

出于测试目的,我创建了一个main()。我使用VisualStudio2019进行测试,并使用C++17。注意,发布的代码没有在C++11中编译。

代码语言:javascript
运行
复制
#include <iostream>
#include <string>
#include <cstdlib>
#include "JSONParser.h"

int main(int argc, char* argv[])
{
    std::string jsonFile;

    if (argc > 1)
    {
        jsonFile = argv[1];
    }
    else
    {
        std::cout << "Please enter a JSON file name." << std::endl;
        std::cin >> jsonFile;
    }

    JSONParser jsonParser;
    try
    {
        jsonParser.parseFile(jsonFile);
    }
    catch (std::runtime_error ex)
    {
        std::cerr << ex.what() << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

我在测试过程中发现了几个可能的问题,有些问题在这里没有列出,因为我没有足够的时间进行调试。

错误检查

没有测试是否找到包含JSON的输入文件,这会导致程序异常终止。由于用户必须指示要在某个地方打开哪个文件,因此需要进行测试,以确定是否可以打开该文件,以及是否可以从中读取该文件。当没有输入文件时,函数std::vector<std::string> JSONParser::lexer(std::string fileName)返回一个空字符串列表,这将导致程序在处理空列表时终止,而不是在输入本身中终止。

lexor的一个可能的替代实现是:

代码语言:javascript
运行
复制
std::vector<std::string> JSONParser::lexer(std::string fileName) {
    char piece;
    std::string capture = "";
    std::string conversion;
    std::vector<std::string> capture_list;

    std::ifstream configfile(fileName);

    piece = configfile.get();
    if (configfile.bad() || configfile.fail())
    {
        std::string emsg("Can't read json from file: ");
        emsg += fileName;
        throw std::runtime_error(emsg);
    }

    while (configfile.good()) {
        if (checkDeliminator(piece)) {
            conversion = piece;
            if (capture != "") {
                capture_list.push_back(capture);
                capture_list.push_back(conversion);
                capture = "";
            }
            else {
                capture_list.push_back(conversion);
            }
        }
        else {
            capture += piece;
        }
        piece = configfile.get();
    }

    configfile.close();

    return capture_list;
}

注意lexor__输入的变化: ifstream configfile仅在函数lexor中使用,所以最好在lexor__的主体中实例化configfile变量。这还允许通过传递文件名来提供更好的错误消息。

可能出现的语法错误或键入

void JSONParser::fsm(std::string value)中有几个if语句可能无法返回正确的结果,而不是使用logical操作符&&||,而是使用了bit操作符|&。这可能导致使用解析器的程序在应该通过时失败,或者在应该失败时传递。还应该注意的是,需要维护代码的任何人都不清楚函数名fsm

算法

快速词法分析器(如lexflex生成的词法分析器)通常使用表作为状态机实现。解析器通常一次接受词法分析器中的单个令牌。解析器生成器(如YACC野牛 )生成下推自动机,它作为状态机使用与堆栈耦合的表。为了提高性能,最好以这种方式实现lexer和解析器。

如果您想尝试使用这些编译器开发工具,可以找到这里的挠曲野牛在这里

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

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

复制
相关文章

相似问题

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