模板引擎:二、实现一个Json解析器

2024-09-01 19:32

本文主要是介绍模板引擎:二、实现一个Json解析器,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

2.Js实现Json解析器

前言

本文主要对Json解析器的实现进行探讨。
如果想深入了解其原理,可以参考上一篇文章:模板引擎:一、理解Json解析器工作原理

项目github地址:https://github.com/yang657850144/parseJson

案例说明

例如:拿一段最简单的Json字符串举例(“{ “a”: 1 }”),要将其解析为JSON对象。

我们先将其进行拆分取出字符串中的特征值(Token),我们可以得到下面七个Token:

    // 以逗号','进行分割", {, "a", :, 1, }, "

然后,通过我们之前定义的数据结构进行匹配:

  • {},以一对大括号包裹的定义为一个对象,并且对象结构是以key-value形式进行存储
  • “”, 以一对双引号包裹的定义为字符串
  • 1, 定义为数值类型

这样,我们就识别出了我们想要的数据结构

{"a": 1
}

思路

通过上面的举例,对Json解析器应该有了基本的理解。
但是,罗马不是一天建成的。接下来我们将逐步完善Json解析器

识别关键字

下面再通过一段代码进行说明,先实现一个简单的关键字解析器


// 定义关键字(Token)
const ENUM = {_TRUE: true,_FALSE: false,_NULL: null,_UNDEFINED: undefined
}let at = 0 // 当前字符所在的下标
let ch = '' // 当前字符let text = '' // 定义一个字符串对象/** * 定义一个字符扫描器 * params: char 传入的为当前扫描的字段* return: 返回当前扫描(at)的一个字符(ch)**/
const getCharAt = (char) => {if(char && char !== ch) {console.error(`当前字符读取错误: ${ch},错误位置: ${at}`)return}ch = text.charAt(at) // 读取当前字符at++ // 指针后移一位return ch
}/*** 关键字扫描器* 功能描述:*   可识别字段(true,false,null,undefined)**/
const keyword = () => {// 通过首字母进行识别switch(ch) {case 't':getCharAt('t')getCharAt('u')getCharAt('r')getCharAt('e')return ENUM._TRUEcase 'f':getCharAt('f')getCharAt('a')getCharAt('l')getCharAt('s')getCharAt('e')return ENUM._FALSEcase 'n':getCharAt('n')getCharAt('u')getCharAt('l')getCharAt('l')return ENUM._NULLcase 'u':getCharAt('u')getCharAt('n')getCharAt('d')getCharAt('e')getCharAt('f')getCharAt('i')getCharAt('n')getCharAt('e')getCharAt('d')return ENUM._UNDEFINED}
}/** * 源字符串* 测试用例: 'true','false','null','undefined'  **/
text = 'null'
// 调用关键字解析器
keyword() // 输出: null

通过上面的关键字解析器,我们可以从源字符串中识别出基本的几个关键字
但是,这个解析器有一个缺陷,它只能精确识别诸如'false'、'null'等无空格的字符串

如果字符串中包含有多个空格(’  null’, ‘      false’),那么我们的解析器就会失效了。

那么,解决的思路有两种

第一种,通过正则匹配,将字符串中的空格进行过滤(str.replace(reg,''))
特点: 高效实用
另一种,实现过滤函数,如果当前字符是空格的话,跳过该字符,指针后移一位(at++)
特点:容易理解

我们通过第二种方式进行讲解

// 接上面的代码
...// 定义一个过滤函数
const filter = () => {while(ch & ch === ' ') {getCharAt()  // 如果当前字符为空格,指针后移一位 at++ }
}/** * 源字符串* 测试用例: '   true','   false','  null','  undefined'  **/
text = '   null'
// 调用过滤函数
filter()
// 调用关键字解析器
keyword() // 输出: null

看到这里,一个简单的关键字解析器已经完成了。是不是有点小激动呢,哈哈,下面我们将慢慢考虑识别更多的数据结构了。

识别数值类型

数值类型的定义:

  • 正数
    • 整型
    • 浮点型
    • 指数型
  • 负数
    • 同上

考虑到篇幅有限,我们暂且只处理整型和浮点型的数值。

/*** 数值类型判断* **/
const number = () => {let str// 识别整型 while(ch && ch >= '0' && ch <= '9') {str += chnext()}// 识别浮点型if(ch === '.') {str += '.'next('.')while(next() && ch >= '0' && ch <= '9') {str += ch   }}return +str // 转换为数值型}/** * 源字符串* 测试用例: '   1','   1.2','  12.34','1234'  **/
text = '  1.2'
// 调用过滤函数
filter()
// 调用数值解析器
number() // 输出: 1.2

我们已经可以识别基本的数字类型了。

不过,下面有种情况,他们也属于数值类型,但是解析器无法识别

+1
+1.2
-1
-1.2

不难看出,我们少了数值符号的判断逻辑。因此,我们添加下面的符号条件判断

/*** 数值符号* return 调用匹配的数值类型,并将符号传入**/
const symbol = () => {if(ch === '+' || ch === '-') {let sym = ch // 识别以'+'、'-'起始的字符next(ch) // 指针后移if(ch && ch >= '0' && ch <= '9' ) {return number(sym) // 进入数值类型判断}}
}

然后我们再重构我们的number函数

const number = (sym = '') => {// 逻辑不变...return sym + (+str)}

通过修改,我们又可以匹配诸如下面几种有符号的数值类型了。

+1
+1.2
-1
-1.2

不过,number函数还是有一个Bug。

如果,输入 1.2abc 或者1a2b 这类不合法的数值类型,我们必须对这种情况进行异常处理。

继续重构我们的number函数

const number = () => {// 同上...// return str + (+val)if(!isFinite(val)) {console.error(`无效的数值类型:${val}`)} else {return str + (+val)}}

这样,我们的Number函数就比较完善了。

识别字符串类型

字符串定义,以一对”“包含的类型。

/***  字符串类型定义*  return 返回一个字符串**/const string = () => {let str// " 起始if(ch === '"') {// 过滤空格filter()next('"')while(next()) {// “ 结尾if(ch === '"') {next('"')return str} else {            str += ch}}}console.error(`无效字符串:${str},位置:${at}`)/** * 源字符串* 测试用例: '"1"','"1a"','"   key"','"  1a."'  **/
text = '"   key"'
// 调用过滤函数
filter()
// 调用数值解析器
string() // 输出: "key"
}

好了,到这里基本数据类型讲解完毕。我们将这三种数据类型整合到一个函数(getValue)中


const getValue = () => {filter()switch(ch) {case '"':return string()case '+':case '-':return symbol()case '[':return array()case '{':return object()default:return (ch && ch >='0' && ch <='9') ? number() : keyword()}}

然后我们开始难度升级,对复合类型的处理(对象、数组)

识别数组

定义:以一对[]包裹,并以‘,’进行分割的数据类型。


const array = () => {let arr = []// 以 [ 起始if(ch && ch === '[') {next('[')filter() // 过滤空格// 识别为空数组if(ch && ch === ']') {return arr}while(next()) {// 递归arr.push(getValue())if(ch === ']') {return arr}filter()// 以 , 将值进行分割if(ch === ',') {next(',')}}}
}

数组匹配的难度在于递归的思想,去遍历数组中的各种数据类型。这也是处理复合类型的统一方法。

识别对象

与数组的判断方式类型,关键区别在于对象的数据格式是以”key-value形式存储”。
而key则必须为一个基本数据类型,本文暂定为字符串类型。

const object = () => {let obj = {}if(ch && ch === '{') {next('{')filter()//  空对象if(ch && ch === '}') {return obj}while(next()) {// 对象的key,类型为字符串let key = string()filter()if(ch && ch === ':') {next(':')if(Object.hasOwnProperty.call(obj,key)) {console.error(`对象关键字重复:${key}`)}// 递归获取对象的valueobj[key] = value()filter()if(ch && ch ==='}') {next('}')return obj}// 以 , 将key-value进行分割if(ch && ch === ',') {next(',')}}}}}

这样,我们的基本Json对象就介绍完毕。

待改进部分

我们这个解析器对数值类型的判断还是不够准确。例如:2e10指数类型没有正确识别。
以及,\t\n 转义字符也未作处理。如果有兴趣,可以继续深入研究下去。谢谢!

可以参考下面的源码进行对比学习

本文github项目地址:https://github.com/yang657850144/parseJson

这里写图片描述

这篇关于模板引擎:二、实现一个Json解析器的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1127838

相关文章

C++中零拷贝的多种实现方式

《C++中零拷贝的多种实现方式》本文主要介绍了C++中零拷贝的实现示例,旨在在减少数据在内存中的不必要复制,从而提高程序性能、降低内存使用并减少CPU消耗,零拷贝技术通过多种方式实现,下面就来了解一下... 目录一、C++中零拷贝技术的核心概念二、std::string_view 简介三、std::stri

C++高效内存池实现减少动态分配开销的解决方案

《C++高效内存池实现减少动态分配开销的解决方案》C++动态内存分配存在系统调用开销、碎片化和锁竞争等性能问题,内存池通过预分配、分块管理和缓存复用解决这些问题,下面就来了解一下... 目录一、C++内存分配的性能挑战二、内存池技术的核心原理三、主流内存池实现:TCMalloc与Jemalloc1. TCM

OpenCV实现实时颜色检测的示例

《OpenCV实现实时颜色检测的示例》本文主要介绍了OpenCV实现实时颜色检测的示例,通过HSV色彩空间转换和色调范围判断实现红黄绿蓝颜色检测,包含视频捕捉、区域标记、颜色分析等功能,具有一定的参考... 目录一、引言二、系统概述三、代码解析1. 导入库2. 颜色识别函数3. 主程序循环四、HSV色彩空间

Python实现精准提取 PDF中的文本,表格与图片

《Python实现精准提取PDF中的文本,表格与图片》在实际的系统开发中,处理PDF文件不仅限于读取整页文本,还有提取文档中的表格数据,图片或特定区域的内容,下面我们来看看如何使用Python实... 目录安装 python 库提取 PDF 文本内容:获取整页文本与指定区域内容获取页面上的所有文本内容获取

基于Python实现一个Windows Tree命令工具

《基于Python实现一个WindowsTree命令工具》今天想要在Windows平台的CMD命令终端窗口中使用像Linux下的tree命令,打印一下目录结构层级树,然而还真有tree命令,但是发现... 目录引言实现代码使用说明可用选项示例用法功能特点添加到环境变量方法一:创建批处理文件并添加到PATH1

Java使用HttpClient实现图片下载与本地保存功能

《Java使用HttpClient实现图片下载与本地保存功能》在当今数字化时代,网络资源的获取与处理已成为软件开发中的常见需求,其中,图片作为网络上最常见的资源之一,其下载与保存功能在许多应用场景中都... 目录引言一、Apache HttpClient简介二、技术栈与环境准备三、实现图片下载与保存功能1.

LiteFlow轻量级工作流引擎使用示例详解

《LiteFlow轻量级工作流引擎使用示例详解》:本文主要介绍LiteFlow是一个灵活、简洁且轻量的工作流引擎,适合用于中小型项目和微服务架构中的流程编排,本文给大家介绍LiteFlow轻量级工... 目录1. LiteFlow 主要特点2. 工作流定义方式3. LiteFlow 流程示例4. LiteF

canal实现mysql数据同步的详细过程

《canal实现mysql数据同步的详细过程》:本文主要介绍canal实现mysql数据同步的详细过程,本文通过实例图文相结合给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的... 目录1、canal下载2、mysql同步用户创建和授权3、canal admin安装和启动4、canal

SpringBoot排查和解决JSON解析错误(400 Bad Request)的方法

《SpringBoot排查和解决JSON解析错误(400BadRequest)的方法》在开发SpringBootRESTfulAPI时,客户端与服务端的数据交互通常使用JSON格式,然而,JSON... 目录问题背景1. 问题描述2. 错误分析解决方案1. 手动重新输入jsON2. 使用工具清理JSON3.

Nexus安装和启动的实现教程

《Nexus安装和启动的实现教程》:本文主要介绍Nexus安装和启动的实现教程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、Nexus下载二、Nexus安装和启动三、关闭Nexus总结一、Nexus下载官方下载链接:DownloadWindows系统根