每日一算法题-JSON模板解析

497 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第30天,点击查看活动详情

一、题目

描述: JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式。
它基于 ECMAScript (欧洲计算机协会制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。
易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。
因此,JSON格式的数据在程序中应用非常广泛。
现在拟做一个能够根据自定义的JSON模板字符串或文件,自动生成JSON文件的工具项目,可用来根据预定义的模板生成任意数量的随机 JSON 值。此工具可帮助开发人员和 QA 以各种输入值测试他们的产品。
为了方便使用,可以将生成的输出 JSON 另存为 .json 文件。

image.png

#include <iostream>
#include <random>
#include <fstream>
#include <ctime>
#include <stdint.h>
#include <list>
#include <vector>
using namespace std;

class JsonTemplate
{
public:
    explicit JsonTemplate(const string& jsonTemplate):
        jsonTemplate(jsonTemplate){}
    string generate(){

    }
    void save(const string& filepath){
    
    }
private:
    string jsonTemplate;
};


int main(int, char**)
{
    string temp =
R"({
   "min_position": "int(3,4,5-9)\",
   "has_more_items": "bool",
   "items_html": "string('Bus','Car','Bike')\",
   "new_latent_count": "int",
   "data": {
      "length": "int(20-30)\",
      "text": "string"
   },
   "numericalArray": [
      "repeat[5]",
      "int(20,23-33)\"
   ],
   "StringArray": [
      "repeat[4]",
      "string('Carbon','Nitrogen','Oxygen')\"
   ],
   "multipleTypesArray": [3,"Hello",5,true],
   "objArray": [
      "repeat[5]",
      {
         "class": "string('middle','upper','lower')\",
         "age": "int"
      }
   ]
})";
    for(int i = 0; i < 6; ++i){
        temp = temp.replace(temp.find("\\"), 1, "");
    }

    JsonTemplate jt(temp);
    cout << jt.generate() << endl;
    jt.save("test.json");
    return 0;
}

二、分析

首先我们知道JSON有三种类型: 对象、列表、值,要解析JSON模板,那就需要分别针对这三种类型进行独立的分析。
值类型是以"开头,和 [ 紧接着有效字符开头
列表类型是以 [ 开头,并且紧接着有个repeat[%d]的个数
对象类型是以 { 开头
这里值类型和列表类型都是以 [ 字符开头,需要区分一下
而列表类型和对象类型都是把特性解析出来之后,剩下的还是要交给值类型处理函数去解析,所以重点还是值类型的解析
值类型有六种: int(),string(),int,string,bool,固定值列表
通过第一个字符可以区分4种,但是int()和int,string()和string还需要判断是否有括号来判断
int()的特殊处理是会有5-9这种范围写法,需要解析成5,6,7,8,9
string的特殊处理是模板里字符串是用单引号包含起来的,而json字符串是以双引号包含起来的
列表类型的特殊处理是,每循环一个值之后,需要加一个逗号
剩下的就是按部就班,一块一块的处理就行了

三、模拟

  1. "min_position": "int(3,4,5-9)"
    这是int()值类型,并且有5-9范围定义,先化简为int(3,4,5,6,7,8,9),然后再通过随机0-6的数通过数组下标索引就好。
  2. "has_more_items": "bool"
    这是bool类型,随机0-1的数,0就是false,1就是true。
  3. "items_html": "string('Bus','Car','Bike')"
    这是string()类型,有3个值,随机0-2的数通过数组下标索引就好。
  4. "new_latent_count": "int"
    这是int类型,没有范围,就取一个随机int数就好。
  5. "text": "string"
    这是string类型,随机字符串就好。
  6. "multipleTypesArray": [3,"Hello",5,true]
    这是固定值列表类型,有4个值,,随机0-3的数通过数组下标索引就好。

四、实现

class JsonTemplate
{
public:
    explicit JsonTemplate(const string& jsonTemplate):
        jsonTemplate(jsonTemplate), engine(static_cast<unsigned int>(time(nullptr))){}
    string generateValue(string& values){
        if(values.at(0) == 'b'){
            return (random(1) > 0 ? "true" : "false");
        }
        bool isInt = (values.at(0) == 'i');
        size_t start = values.find('(');
        if(start > values.size()){
            if(isInt){
                return to_string(random(1000));
            }else{
                string r;
                r.push_back('"');
                r.push_back(random('Z' -'A') + 'A');
                r.push_back('"');
                return r;
            }
        }else{
            vector<string> datas;
            string data;
            for(int i = start + 1; i < values.size() - 1; ++i){
                if(values.at(i) != ','){
                    data.push_back(values.at(i));
                }else{
                    datas.push_back(data);
                    data.clear();
                }
            }
            datas.push_back(data);
            if(isInt){
                for(int i = datas.size() - 1; i >= 0; --i){
                    size_t index = datas.at(i).find('-');
                    if(index > datas.at(i).size()) continue;
                    int start = atoi(datas.at(i).substr(0,index).data()),
                        end = atoi(datas.at(i).substr(index + 1, datas.at(i).size() - index - 1).data());
                    datas[i] = to_string(start);
                    for(int j = start + 1; j <= end; ++j){
                        datas.push_back(to_string(j));
                    }
                }
            }else{
                for(int i = datas.size() - 1; i >= 0; --i){
                    datas[i][0] = '"';
                    datas[i][datas.at(i).size() - 1] = '"';
                }
            }
            return datas.at(random(datas.size() - 1));
        }
    }
    void generateObject(string& jsonResult, uint32_t& cursor){
        while(true){
            while(jsonTemplate.at(cursor) != ':'){
                jsonResult.push_back(jsonTemplate.at(cursor));
                ++cursor;
            }
            jsonResult.push_back(jsonTemplate.at(cursor));
            ++cursor;

            while(!isValidChar(cursor)){
                jsonResult.push_back(jsonTemplate.at(cursor));
                ++cursor;
            }

            if(jsonTemplate.at(cursor) == '"'){
                string values;
                ++cursor;
                while(jsonTemplate.at(cursor) != '"'){
                    values.push_back(jsonTemplate.at(cursor));
                    ++cursor;
                }
                ++cursor;
                jsonResult.append(generateValue(values));
            }else if(jsonTemplate.at(cursor) == '['){
                uint32_t cur = cursor + 1;
                if(isValidChar(cur)){
                    ++cursor;
                    vector<string> datas;
                    string data;
                    while(jsonTemplate.at(cursor) != ']'){
                        if(jsonTemplate.at(cursor) != ','){
                            data.push_back(jsonTemplate.at(cursor));
                        }else{
                            datas.push_back(data);
                            data.clear();
                        }
                        ++cursor;
                    }
                    ++cursor;
                    datas.push_back(data);
                    jsonResult.append(datas.at(random(datas.size() - 1)));
                }else{
                    jsonResult.push_back(jsonTemplate.at(cursor));
                    ++cursor;
                    generateList(jsonResult, cursor);
                }
            }else{
                jsonResult.push_back(jsonTemplate.at(cursor));
                ++cursor;
                generateObject(jsonResult, cursor);
            }

            while(!isValidChar(cursor)){
                jsonResult.push_back(jsonTemplate.at(cursor));
                 ++cursor;
            }
            if(jsonTemplate.at(cursor) == '}'){
                jsonResult.push_back(jsonTemplate.at(cursor));
                ++cursor;
                break;
            }
            jsonResult.push_back(jsonTemplate.at(cursor));
            ++cursor;
        }
    }
    void generateList(string& jsonResult, uint32_t& cursor){
        while(jsonTemplate.at(cursor) != '[') ++cursor;
        ++cursor;
        string inte;
        while(jsonTemplate.at(cursor) != ']'){
            inte.push_back(jsonTemplate.at(cursor));
             ++cursor;
        }
        int listCount = atoi(inte.data());
        while(jsonTemplate.at(cursor) != ',') ++cursor;
        ++cursor;
        int listCursor = cursor;
        while(listCount){
            cursor = listCursor;

            while(!isValidChar(cursor)){
                jsonResult.push_back(jsonTemplate.at(cursor));
                ++cursor;
            }
            if(jsonTemplate.at(cursor) == '{'){
                jsonResult.push_back(jsonTemplate.at(cursor));
                ++cursor;
                generateObject(jsonResult, cursor);
            }else if(jsonTemplate.at(cursor) == '['){
                jsonResult.push_back(jsonTemplate.at(cursor));
                ++cursor;
                generateList(jsonResult, cursor);
            }else{
                string values;
                ++cursor;
                while(jsonTemplate.at(cursor) != '"'){
                    values.push_back(jsonTemplate.at(cursor));
                    ++cursor;
                }
                ++cursor;
                jsonResult.append(generateValue(values));
            }
            --listCount;
            if(listCount > 0){
                jsonResult.push_back(',');
            }
        }
        while(jsonTemplate.at(cursor) != ']'){
            jsonResult.push_back(jsonTemplate.at(cursor));
             ++cursor;
        }
        jsonResult.push_back(jsonTemplate.at(cursor));
        ++cursor;
    }
    string generate(){
        uint32_t cursor = 0;
        string jsonResult;
        while(!isValidChar(cursor)){
            jsonResult.push_back(jsonTemplate.at(cursor));
            ++cursor;
        }
        jsonResult.push_back(jsonTemplate.at(cursor));
        if(jsonTemplate.at(cursor) == '{'){
            ++cursor;
            generateObject(jsonResult, cursor);
        }else{
            ++cursor;
            generateList(jsonResult, cursor);
        }
        for(; cursor < jsonTemplate.size(); ++cursor){
            jsonResult.push_back(jsonTemplate.at(cursor));
        }
        return jsonResult;
    }
    bool isValidChar(uint32_t& cursor){
        return (jsonTemplate.at(cursor) > ' ' && jsonTemplate.at(cursor) < 127);
    }
    void save(const string& filepath){
        std::ofstream ostr(filepath);
        string data = generate();
        ostr.write(data.data(), data.size());
    }
    uint32_t random(uint32_t range){
        return std::uniform_int_distribution<unsigned>(0, range)(engine);
    }
private:
    string jsonTemplate;
    std::default_random_engine engine;
};

五、结言

json格式是现代编程中必不可少的一种交流格式,在web开发中更是起着举足轻重的作用,在测试中经常需要用到大量的测试用例,手动编写又非常麻烦,这时通过固定的模板,批量生成大量的json数据,可以大大缩减测试的工作量。
对于模板的解析工作,更好的解决办法是分解成一个个节点,再生成的时候直接通过遍历节点就可以快速生成,但是这种方法需要先对上诉方法就是很好的理解之后才能游刃有余,不然很容易弄错,就像要先学会面向过程,才能更好的理解面向对象。
解题是为了更好的去解决生活中所遇到的各种难题,并且更快更好的摆平。

创作不易,留个赞再走吧!如果对文章内容有任何指正,欢迎评论!