关于javascript:js栈思想实现解析json字符串功能

62次阅读

共计 8324 个字符,预计需要花费 21 分钟才能阅读完成。

给你一个 json 字符串,在不应用 evalJSON.parsenew Function 的状况下本人写函数解析成 json 对象,你会怎么实现呢?

json 的一个特点是数组与对象之间能够互相嵌套,并且有限层级的嵌套,这也是解析 json 最麻烦的中央。

1、思路:栈思维

“栈”的个性:先进后出 (栈只有一个入口和进口,入口就是进口)
现实生活中的物体形容栈: 乒乓球桶,它只有一端能够关上,另一端是关闭的,最先放进去的乒乓球最初能力拿的进去
数据结构: 在 JavaScript 中实现“栈”的最好数据结构是 数组
思路:

  • 1、定义两个栈,栈 1(stack1)用例存储 json 对象,栈 2(stack2)用来存储 json 对象的 key 和 value;定义一个变量用来存储双引号的数量dabbleQuotCount;定义一个变量保留截取的字符串
  • 2、循环整个 json 字符串,而后一个一个字符的截取 json 字符串的第一个字符

    • 判断第一个字符是否以 {[ 结尾,如果是以它们结尾,则示意遇到了一个对象或数组,此时须要往 栈 1 中增加一个空对象或空数组,往 栈 2 中增加一个空数组
    • 判断第一个字符是否以双引号 (") 结尾,判断双引号的数量是否为 2,为 2 就阐明后面截取的字符串要么是对象的 key,要么是对象的 value,或者是数组的 value;如果不是则持续循环
    • 判断第一个字符是否以逗号 (,) 结尾,如果是则示意后面截取的字符串为对象的值或数组的项
    • 判断第一个字符是否以}] 结尾,如果是则示意一个对象或数组完结了
    • 以其余字符串结尾,啥都不做,持续循环

1、代码实现

function jsonParser(jsonString){
  let surplusStr = jsonString; // 残余局部
  let firstChar = surplusStr.charAt(0);
  let lastChar = surplusStr.charAt(surplusStr.length - 1);
  if((firstChar !== '{' && lastChar !== '}') && (firstChar !== '[' && lastChar !== ']')){throw new Error(`${jsonString}不是一个规范的 json 对象 `);
  }

  let jsonObjStack = []; // json 对象栈
  let keyValuesStack = []; // 存储 json 对象的键和值的栈(二维数组),它外面的值还是一个数组,该数组中寄存对象,对象里保留着键和值
  let resultJson = null;

  let dabbleQuotCount = 0; // 双引号数量
  let currentStr = ''; // 以后截取的字符
  // 截取字符串
  let substr = function (needStoreStr, splitFrom, splitLength){if(needStoreStr.length > 0){currentStr += needStoreStr;}
    surplusStr = surplusStr.substr(splitFrom, splitLength);
  }
  // 去除字符串两端双引号
  let trimQuot = function (str) {return str.replace(/(^"?)|("?$)/g, '');
  }
  // 存储 value 到栈顶
  let storeValueToStackTop = function (keyValuesStackTop, jsonObjStackTop, value) {if(Array.isArray(jsonObjStackTop)){keyValuesStackTop.push(value);
      console.log('进栈', keyValuesStackTop, value);
    } else {
      // 获取栈顶的最初一项
      let stackTopLast = keyValuesStackTop[keyValuesStackTop.length - 1];
      stackTopLast.value = value;
      console.log('进栈', stackTopLast, value);
    }
  }
  // 解决 currentStr,转换根本数据类型
  let handleCurrentStr = function (str) {if(!isNaN(Number(str))){str = Number(str);
    }else if(str == 'null'){str = null;}else if(str == 'undefined'){str = undefined;}else if(str == 'true' || str == 'false'){str = str == 'true';}
    return str;
  }
  // json 对象栈、keyValuesStack 栈最初一位出栈
  let stackPop = function () {let jsonObjStackTop = jsonObjStack.pop();
    let keyValuesStackTop = keyValuesStack.pop();
    console.log('出栈', keyValuesStackTop);
    if(typeof jsonObjStackTop === 'undefined' || typeof keyValuesStackTop === 'undefined') {return;}
    if(Array.isArray(jsonObjStackTop)){
      keyValuesStackTop.forEach(item => {jsonObjStackTop.push(item);
      });
    }else {
      keyValuesStackTop.forEach(item => {jsonObjStackTop[item.key] = item.value;
      });
    }
    resultJson = jsonObjStackTop;
  }

  while (surplusStr.length > 0) {let firstChar = surplusStr.charAt(0);

    if(firstChar === '{' || firstChar === '['){ // 第一个字符为“{”则示意遇到一个对象,此时须要增加一个空对象到 jsonObjStack 中,并且增加一个存储键值的空数组到 keyValues 中
      if(firstChar === '{'){console.log('首字符为开始大括号');
      }else {console.log('首字符为开始中括号');
      }

      let keyValuesStackTop = keyValuesStack[keyValuesStack.length - 1];
      let jsonObjStackTop = jsonObjStack[jsonObjStack.length - 1];
      let obj = firstChar === '{' ? {} : [];
      // 每次遇到一个对象就须要往 jsonObjStack 中增加一个新对象,并且视状况往 keyValuesStack 栈中增加一个空数组,否则往其父对象中增加
      if(typeof jsonObjStackTop !== 'undefined') {if (Array.isArray(jsonObjStackTop)) {keyValuesStackTop.push(obj);
        } else {let lastKeyValueObj = keyValuesStackTop[keyValuesStackTop.length - 1];
          if (typeof lastKeyValueObj.value === 'undefined') {lastKeyValueObj.value = obj;}
        }
      }

      jsonObjStack.push(obj);
      keyValuesStack.push([]);
      surplusStr = surplusStr.substr(1);
    } else if(firstChar === '"'){ // 判断为双引号的状况
      console.log('首字符为双引号');
      // 获取以后截取的字符的最初一个字符
      let currentStrLast = typeof currentStr !== 'string' ? '' : currentStr.charAt(currentStr.length - 1);
      if(currentStrLast !== '\\'){ // 如果以后截取的字符最初面一个字符不是“\”则阐明不是本义双引号,即不是 "ab\"cd" 这种状况
        console.log('首字符为双引号,不是“\\”');
        dabbleQuotCount++;
        if(dabbleQuotCount < 2){console.log('首字符为双引号,未凑成一对双引号');
          substr(firstChar, 1);
        } else { // 如果双引号的数量为 2,有可能是对象的 key,或是对象的 value,或是数组的 value
          substr(firstChar, 1);
          dabbleQuotCount = 0;
          // 获取紧挨着第一个字符前面的相邻字符
          let nextStr = surplusStr.charAt(0);
          // 获取栈顶存储的 json 对象键值
          let keyValuesStackTop = keyValuesStack[keyValuesStack.length - 1];
          console.log('首字符为双引号,凑成一对双引号,值为:', currentStr, keyValuesStackTop);
          if(nextStr === ':'){ // 如果 nextStr 为冒号,则 currentStr 为对象的 key
            console.log('首字符为双引号,凑成一对双引号,下一个字符为冒号');
            keyValuesStackTop.push({key: trimQuot(currentStr)
            });
            currentStr = '';
            substr('', 1);
          }else if(nextStr === ',' || nextStr === ']' || nextStr === '}'){// 如果 nextStr 为逗号或]或},则 currentStr 为对象或数组的值
            let jsonObjStackTop = jsonObjStack[jsonObjStack.length - 1];
            console.log('首字符为双引号,凑成一对双引号,下一个字符为:', nextStr, keyValuesStackTop);
            storeValueToStackTop(keyValuesStackTop, jsonObjStackTop, trimQuot(currentStr));
            if(nextStr === ','){substr('', 1);
            }
            currentStr = '';
          }
        }
      } else {console.log('首字符为双引号,是“\\”');
        substr(firstChar, 1);
      }
    } else if(firstChar === '}' || firstChar === ']') {// 遇到}完结花括号则示意一个对象完结了
      if(firstChar === '{'){console.log('首字符为完结大括号');
      }else {console.log('首字符为完结中括号');
      }
      if(currentStr){if(firstChar === '{'){console.log('首字符为完结大括号,并且 currentStr 有值,值为:', currentStr);
        }else {console.log('首字符为完结中括号,并且 currentStr 有值,值为:', currentStr);
        }

        let jsonObjStackTop = jsonObjStack[jsonObjStack.length - 1];
        let keyValuesStackTop = keyValuesStack[keyValuesStack.length - 1];
        let tempVal = handleCurrentStr(currentStr);
        storeValueToStackTop(keyValuesStackTop, jsonObjStackTop, tempVal);
        currentStr = '';
      }
      stackPop();
      substr('', 1);
    } else if(firstChar === ',') { // 遇到逗号则示意后面截取的字符串为对象的值或数组的项
      console.log('首字符为逗号');
      if(dabbleQuotCount == 0){ // 如果双引号的数量为 0,则阐明逗号未被双引号包着,之前截取的字符串为对象的 value 或数组项
        console.log('首字符为逗号,逗号不在双引号内,值为:', currentStr);
        if(currentStr){let keyValuesStackTop = keyValuesStack[keyValuesStack.length - 1];
          let jsonObjStackTop = jsonObjStack[jsonObjStack.length - 1];
          // console.log('------------keyValuesStackTop', keyValuesStack);
          let tempVal = handleCurrentStr(currentStr);
          storeValueToStackTop(keyValuesStackTop, jsonObjStackTop, tempVal);
        }
        currentStr = '';
        substr('', 1);
      } else {console.log('首字符为逗号,逗号在双引号内');
        substr(firstChar, 1);
      }
    } else {console.log('首字符为其余');
      substr(firstChar, 1);
    }
  }

  console.log('resultJson', resultJson);
  if(jsonObjStack.length > 0 || keyValuesStack.length > 0){throw new Error('json 解析失败!');
  }
  return resultJson;
}

3、测试一下

经测试,以下字符串均可正确解析进去!

let jsonStrSimple = '{"name":" 张三 ","age":23,"man":true,"cleanliness":null}';
let jsonStrWithObj = '{"name":" 张三 ","age":23,"man":true,"cleanliness":null,"score":{" 语文 ":80," 数学 ":95}}';
let jsonStrWithObj2 = '{"name":" 张三 ","age":23,"man":true,"cleanliness":null,"subject":{" 语文 ":{"teacher":" 李老师 ","score":80}," 数学 ":{"teacher":" 王老师 ","score":95}}}';
let jsonStr = '{"statusCode":200,"comments":" 胜利 ","data":{"adminUserId":"61973a868fef766ab4ba953b","roleId":"61973a868fef766ab4ba953c","roleName":null,"orgId":"61973a878fef766ab4ba9686","orgName":"liyn","username":"liyn","email":null,"cellphone":null,"name":null,"nickname":" 超级管理员 ","idcard":null,"adminType":{"code":1,"displayName":" 永恒账户 ","name":"PERMANENT"},"adminStatus":{"code":1,"displayName":" 已激活 ","name":"AVAILABLE"},"adminStatusTime":"2021-11-19 13:47:51","loginMode":null,"permitLoginTime":null,"permitLoginIp":null,"online":true,"updatedTime":"2022-01-05 17:41:16","updatedBy":"5da7d124ce5b3053a8e9838d","createdTime":"2021-11-19 13:47:50","createdBy":"5da7d124ce5b3053a8e9838d","adminTypeTime":null,"defaultFlag":false,"loginFailtures":0,"authType":{"code":1,"displayName":" 默认策略 ","name":"DEFAULT"},"sex":null,"permitLoginStart":null,"permitLoginEnd":null,"defaultPassword":false,"delFlag":false,"subject":null,"credible":false,"companyId":"61973a868fef766ab4ba953a","domain":"liyn","logoUrl":"","shortName":"liyn","emailDomain":"","roleVo":{"roleId":"61973a868fef766ab4ba953c","roleName":" 机构超级管理员 ","roleStatus":{"code":1,"displayName":" 启用 ","name":"AVAILABLE"},"roleType":{"code":4,"displayName":" 超级模块 ","name":"SUPER"},"roleCategory":null,"permission":null,"remark":" 默认的机构超级管理员 ","createdBy":"61973a868fef766ab4ba953b","createdTime":null,"updatedBy":null,"updatedTime":"2021-11-19 13:47","defaultFlag":true,"delFlag":false,"credible":false,"menuIds":null}}}';


let jsonStrWithArr = '{"name":" 张三 ","age":23,"man":true,"cleanliness":null,"hobby":[" 学习 "," 看电影 "," 打球 "]}';
let jsonStrWithArr2 = '{"name":" 张三 ","age":23,"man":true,"cleanliness":null,"score":{" 语文 ":80," 数学 ":95},"hobby":[" 学习 "," 看电影 "," 打球 "]}';
let jsonStrWithArr3 = '{"name":" 张三 ","age":23,"hobby":[" 学习 ",2022," 看电影 ",true," 打球 ",null,{"hobbyA":123,"arr":[" 手工 "," 解析 ","json",456,[" 嵌套的数组 "],undefined,true]}]}';

let arrJson = '[" 手工 "," 解析 ","json"]';
let arrJson1 = '[" 学习 ",2022," 看电影 ",true," 打球 ",null,{"hobbyA":123}]';
let arrJson2 = '[" 学习 ",2022," 看电影 ",true," 打球 ",null,{"hobbyA":123,"arr":[" 手工 "," 解析 ","json",456,[" 嵌套的数组 "],undefined,true]}]';


// jsonParser(jsonStrSimple);
// jsonParser(jsonStrWithObj);
// jsonParser(jsonStrWithObj2);
jsonParser(jsonStr);

// jsonParser(jsonStrWithArr);
// jsonParser(jsonStrWithArr2);
// jsonParser(jsonStrWithArr3);

// jsonParser(arrJson);
// jsonParser(arrJson1);
// jsonParser(arrJson2);

正文完
 0