JSON.stringify(遞歸)與?JSON.parse(有限狀態(tài)自動(dòng)機(jī))的實(shí)現(xiàn)代碼

JSON 的 stringify 和 parse 兩個(gè)方法在平時(shí)的工作中也很常用,如果沒(méi)有一些特殊的類(lèi)型,是實(shí)現(xiàn)數(shù)據(jù)深拷貝的一個(gè)原生方式。
下面就這兩個(gè)方法的一個(gè)手動(dòng)實(shí)現(xiàn)思路。
JSON.stringify
JSON.stringify 方法用于將 JavaScript 值轉(zhuǎn)換為 JSON 字符串。該方法有三個(gè)參數(shù):
- data: 需要轉(zhuǎn)換的數(shù)據(jù)
- replacer:用于轉(zhuǎn)換結(jié)果的對(duì)象或者數(shù)組,可以函數(shù)或者數(shù)組
- space:文本添加縮進(jìn)、空格和換行符,可以是數(shù)字或者字符串,數(shù)字的最大值是 10,字符串的最大長(zhǎng)度是 10
下面的測(cè)試只用到這些類(lèi)型: number,string,function,object,array,null,undefined,map,set,weakmap,weakset
但是 JavaScript 數(shù)據(jù)的嚴(yán)格類(lèi)型遠(yuǎn)遠(yuǎn)不止這幾個(gè)。
data
首先我們用 JSON.stringify 來(lái)打印結(jié)果:
const testJson = {
4: 3,
n: 1,
s: 's',
f: () => { },
null: null,
unde: undefined,
arr: [1, 's', null, undefined, () => { }],
obj: {
n: '1',
s: 's'
},
map: new Map(),
set: new Set([1, 2, 3]),
wmap: new WeakMap(),
wset: new WeakSet()
}
const raws = JSON.stringify(testJson)
// {
// "4":3,"n":1,"s":"s","null":null,"arr":[1,"s",null,null,null],
// "obj":{"n":"1","s":"s"},"map":{},"set":{},"wmap":{},"wset":{}
// }根據(jù)上面的結(jié)果,我們可以發(fā)現(xiàn)對(duì)象內(nèi)的 function, undefined 被剔除了,map, set 等都被動(dòng)的轉(zhuǎn)換成了空對(duì)象。而數(shù)組內(nèi)的 function 和 undefined 被替換成了 null。
所以我們可以根據(jù)上述規(guī)則寫(xiě)一個(gè)簡(jiǎn)單的 stringify 方法:
const stringify = (data: any) => {
// 獲取數(shù)據(jù)的嚴(yán)格類(lèi)型
const type = getType(data)
let res = ''
switch (type) {
case 'Object':
// 處理對(duì)象
res = stringifyObject(data)
break
case 'Array':
// 處理數(shù)組
res = stringifyArray(data)
break
case 'Number':
case 'Boolean':
res = `${data}`
break
case 'String':
res = `"${data}"`
break
case 'Null':
res = 'null'
break
case 'Set':
case 'WeakSet':
case 'Map':
case 'WeakMap':
res = '{}'
break
default:
return
}
return res
}實(shí)現(xiàn)幾個(gè)輔助函數(shù):
// 獲取嚴(yán)格類(lèi)型
const getType = (data: any) => {
return Object.prototype.toString.call(data).slice(8, -1)
}
// 處理對(duì)象方法
const stringifyObject = (data: Record<string, any>) => {
const vals: string[] = []
for (const key in data) {
// 遞歸處理
const val = stringify(data[key])
// 如果值為 undefined,我們則需要跳過(guò)
if (val !== undefined) {
vals.push(`"${key}":${val}`)
}
}
return `{${vals.join(',')}}`
}
// 處理數(shù)組方法
const stringifyArray = (data: any[]) => {
const vals: any[] = []
for (const val of data) {
// 遞歸處理,如果返回 undefined 則替換為 null
vals.push(stringify(val) || 'null')
}
return `[${vals.join(',')}]`
}到這里就實(shí)現(xiàn)了 stringify 的簡(jiǎn)單版本。下面可以簡(jiǎn)單測(cè)試一下:
const raws = JSON.stringify(testJson) const cuss = stringify(testJson) console.log(raws === cuss) // true
后面還有兩個(gè)參數(shù),我們先實(shí)現(xiàn)第三個(gè),第二個(gè)參數(shù)的作用等下在實(shí)現(xiàn)。
space
space 主要是用于添加空格、換行、縮進(jìn),但是只要 space 的值是合法的,換行符是默認(rèn)加上一個(gè)的。所以我們要改下 stringify 的方法:
type Replacer = ((key: string, value: any) => any) | null | (string | number)[]
export const stringify = (data: any, replacer?: Replacer, space?: number | string, indent = 1) => {
const type = getType(data)
if (typeof space === 'number') {
if (space <= 0) {
space = undefined
} else {
space = Math.min(10, space)
}
} else if (typeof space === 'string') {
space = space.substring(0, 10)
} else if (space) {
space = undefined
}
let res = ''
switch (type) {
case 'Object':
res = stringifyObject(data, indent, replacer, space)
break
case 'Array':
res = stringifyArray(data, indent, replacer, space)
break
// 省略部分代碼
}
// 省略部分代碼
}對(duì)于 space 的不同非法的值,我們可以在控制臺(tái)上進(jìn)行一些簡(jiǎn)單的測(cè)試就可以得出,像 -1 這種其實(shí)是不生效的。而我處理的是只能是數(shù)字和字符串,數(shù)字必須是 1 - 10,字符串的最長(zhǎng)長(zhǎng)度是 10 位,其余的都重置為 undefined。因?yàn)橄駭?shù)組和對(duì)象的這種嵌套,縮進(jìn)其實(shí)是要跟著動(dòng)的,這里就新增了 indent 字段,初始為 1,后續(xù)遞歸就 + 1。
// 新增分隔符處理方法
const handleSeparator = (space: number | string, indent: number, prefix: string = '', suffix: string = '') => {
let separator = prefix + '\n'
if (typeof space === 'number') {
separator += ' '.repeat(space).repeat(indent)
} else {
separator += space.repeat(indent)
}
return separator + suffix
}
// 對(duì)象方法修改
const stringifyObject = (data: Record<string, any>, indent: number, replacer?: Replacer, space?: number | string) => {
const vals: string[] = []
for (const key in data) {
const val = stringify(data[key], replacer, space, indent + 1)
if (val !== undefined) {
vals.push(`"${key}":${space ? ' ' : ''}${val}`)
}
}
// 新增 space 處理
if (space) {
const val = vals.join(handleSeparator(space, indent, ','))
if (!val) {
return '{}'
}
const front = handleSeparator(space, indent, '{')
const back = handleSeparator(space, indent - 1, '', '}')
return front + val + back
}
return `{${vals.join(',')}}`
}
// 數(shù)組處理方法
const stringifyArray = (data: any[], indent: number, replacer?: Replacer, space?: number | string) => {
const vals: any[] = []
for (const val of data) {
vals.push(stringify(val, replacer, space, indent + 1) || 'null')
}
// 新增 space 處理
if (space) {
const front = handleSeparator(space, indent, '[')
const val = vals.join(handleSeparator(space, indent, ','))
const back = handleSeparator(space, indent - 1, '', ']')
return front + val + back
}
return `[${vals.join(',')}]`
}replacer
replacer 參數(shù)有兩個(gè)類(lèi)型:
- 數(shù)組類(lèi)型是用來(lái)過(guò)濾對(duì)象類(lèi)型內(nèi)的字段,只保留數(shù)組內(nèi)的 key
- 函數(shù)類(lèi)型就是在數(shù)組和對(duì)象遍歷的時(shí)候,開(kāi)發(fā)者可以自定義某些類(lèi)型的字符串方式
所以這里我們需要修改三處地方:
// 起始方法增加一個(gè)是否是第一次調(diào)用的標(biāo)記 init
export const stringify = (data: any, replacer?: Replacer, space?: number | string, indent = 1, init = true) => {
// 如果 replacer 為函數(shù)的話,初始 key 為空串,值為 data
if (typeof replacer === 'function' && init) {
return stringify(replacer('', data), replacer, space, indent, false)
}
const type = getType(data)
// 省略部分代碼
}
const stringifyObject = (data: Record<string, any>, indent: number, replacer?: Replacer, space?: number | string) => {
const filter = getType(replacer) === 'Array' ? replacer : null
const vals: string[] = []
for (const key in data) {
// 補(bǔ)全參數(shù),修改 replacer 處理
const val = stringify(
typeof replacer === 'function' ? replacer(key, data[key]) : data[key],
replacer,
space,
indent + 1,
false
)
if (
val !== undefined &&
(
!filter ||
(filter as (string | number)[]).includes(key) ||
(filter as (string | number)[]).includes(+key)
)
) {
vals.push(`"${key}":${space ? ' ' : ''}${val}`)
}
}
// 省略部分代碼
}
const stringifyArray = (data: any[], indent: number, replacer?: Replacer, space?: number | string) => {
const vals: any[] = []
let i = 0
for (const val of data) {
// 補(bǔ)全參數(shù),修改 replacer 處理
vals.push(stringify(
typeof replacer === 'function' ? replacer(i++, val) : val,
replacer,
space,
indent + 1,
false
) || 'null')
}
if (space) {
const front = handleSeparator(space, indent, '[')
const val = vals.join(handleSeparator(space, indent, ','))
const back = handleSeparator(space, indent - 1, '', ']')
return front + val + back
}
return `[${vals.join(',')}]`
}到這里, stringify 的方法差不多了。下面是完整代碼:
type Replacer = ((key: string | number, value: any) => any) | null | (string | number)[]
const getType = (data: any) => {
return Object.prototype.toString.call(data).slice(8, -1)
}
const handleSeparator = (space: number | string, indent: number, prefix: string = '', suffix: string = '') => {
let separator = prefix + '\n'
if (typeof space === 'number') {
separator += ' '.repeat(space).repeat(indent)
} else {
separator += space.repeat(indent)
}
return separator + suffix
}
const stringifyObject = (data: Record<string, any>, indent: number, replacer?: Replacer, space?: number | string) => {
const filter = getType(replacer) === 'Array' ? replacer : null
const vals: string[] = []
for (const key in data) {
const val = stringify(
typeof replacer === 'function' ? replacer(key, data[key]) : data[key],
replacer,
space,
indent + 1,
false
)
if (
val !== undefined &&
(
!filter ||
(filter as (string | number)[]).includes(key) ||
(filter as (string | number)[]).includes(+key)
)
) {
vals.push(`"${key}":${space ? ' ' : ''}${val}`)
}
}
if (space) {
const val = vals.join(handleSeparator(space, indent, ','))
if (!val) {
return '{}'
}
const front = handleSeparator(space, indent, '{')
const back = handleSeparator(space, indent - 1, '', '}')
return front + val + back
}
return `{${vals.join(',')}}`
}
const stringifyArray = (data: any[], indent: number, replacer?: Replacer, space?: number | string) => {
const vals: any[] = []
let i = 0
for (const val of data) {
vals.push(stringify(
typeof replacer === 'function' ? replacer(i++, val) : val,
replacer,
space,
indent + 1,
false
) || 'null')
}
if (space) {
const front = handleSeparator(space, indent, '[')
const val = vals.join(handleSeparator(space, indent, ','))
const back = handleSeparator(space, indent - 1, '', ']')
return front + val + back
}
return `[${vals.join(',')}]`
}
export const stringify = (data: any, replacer?: Replacer, space?: number | string, indent = 1, init = true): string | undefined => {
if (typeof replacer === 'function' && init) {
return stringify(replacer('', data), replacer, space, indent, false)
}
const type = getType(data)
if (typeof space === 'number') {
if (space <= 0) {
space = undefined
} else {
space = Math.min(10, space)
}
} else if (typeof space === 'string') {
space = space.substring(0, 10)
} else if (space) {
space = undefined
}
let res = ''
switch (type) {
case 'Object':
res = stringifyObject(data, indent, replacer, space)
break
case 'Array':
res = stringifyArray(data, indent, replacer, space)
break
case 'Number':
res = `${data}`
break
case 'Boolean':
res = `${data}`
break
case 'String':
res = `"${data}"`
break
case 'Null':
res = 'null'
break
case 'Set':
case 'WeakSet':
case 'Map':
case 'WeakMap':
res = '{}'
break
default:
return
}
return res
}JSON.parse
stringify 方法的實(shí)現(xiàn)還是比較簡(jiǎn)單的,在一些筆試中還有可能會(huì)有相關(guān)需要實(shí)現(xiàn)的題。
而 JSON.parse 則是需要將合法的 json 字符串轉(zhuǎn)換成對(duì)象,這里就需要用到一個(gè)概念:有限狀態(tài)自動(dòng)機(jī)
有限狀態(tài)自動(dòng)機(jī)
這里只做簡(jiǎn)單的介紹:有限狀態(tài)機(jī)(Finite State Machine),是指任意時(shí)刻都處于有限狀態(tài)集合中的某一狀態(tài)。當(dāng)其獲得一個(gè)輸入字符時(shí),將從當(dāng)前狀態(tài)轉(zhuǎn)換到另一個(gè)狀態(tài)或者仍然保持當(dāng)前狀態(tài)。
可以結(jié)合當(dāng)前 json 字符串的場(chǎng)景來(lái)簡(jiǎn)單理解一下:
我們有如下一個(gè)字符串:
const str = '{"4":3,"s":"s","null":null,"arr":[1,"s",null],"obj":{}}'然后定義幾個(gè)狀態(tài):
const State = {
INIT: 'INIT', // 初始狀態(tài)
OBJECTSTART: 'OBJECTSTART', // 開(kāi)始解析對(duì)象
ARRAYSTART: 'ARRAYSTART', // 開(kāi)始解析數(shù)組
OBJVALSTART: 'OBJVALSTART', // 開(kāi)始解析對(duì)象的屬性與值
OBJVALEND: 'OBJVALEND', // 對(duì)象屬性與值解析結(jié)束
ARRVALSTART: 'ARRVALSTART' // 開(kāi)始解析數(shù)組值
}因?yàn)?json 字符串是非常規(guī)則的字符串,所以我們可以結(jié)合正則表達(dá)式來(lái)提取相關(guān)步驟的數(shù)據(jù),在字符串中的 ' '\t\n\r 等也是可以的,所以在正則中需要考慮并且替換。
const parse = (data: string | number | null || boolean) => {
if (typeof data === 'number' || data === null || typeof data === 'boolean') {
return data
}
// 將字符串轉(zhuǎn)換為地址引用,方便后面字符串?dāng)?shù)據(jù)的消費(fèi)
const context = { data }
// 具體解析方法
return parseData(context)
}然后定義幾個(gè)輔助函數(shù):
// 字符串的消費(fèi)函數(shù) - 就是截取已匹配完的數(shù)據(jù),返回剩余字符串
const advance = (context: { data: string }, num: number) => {
context.data = context.data.slice(num)
}
// 是否結(jié)束狀態(tài)機(jī)
const isEnd = (ctx: { data: string }) => {
// 如果沒(méi)有數(shù)據(jù)了,則結(jié)束
if (!ctx.data) {
return false
}
const match = /^([}\]])[ \t\n\r]*/.exec(ctx.data)
if (match) {
if (
match[1] === '}' && getType(res) !== 'Object' ||
match[1] === ']' && getType(res) !== 'Array'
) {
throw Error('解析錯(cuò)誤')
}
advance(ctx, match[0].length)
return false
}
return true
}
// 處理值
const parseValue = (context: { data: string }, match: any[]) => {
advance(context, match[0].length)
const valMatch = /^"(.*?)"$/.exec(match[1])
if (valMatch) {
return valMatch[1]
}
if (match[1] === 'null') {
return null
}
if (match[1] === 'true') {
return true
}
if (match[1] === 'false') {
return false
}
if (isNaN(+match[1])) {
throw Error('解析錯(cuò)誤')
}
return Number(match[1])
}
// 解析對(duì)象屬性值
const parseObjValue = (context: { data: string }) => {
const match = /^[ \n\t\r]*((".*?")|([0-9A-Za-z]*))[ \t\n\r]*/.exec(context.data)
if (match) {
return parseValue(context, match)
}
new Error('解析錯(cuò)誤')
}
// 解析數(shù)組值
const parseArrValue = (context: { data: string }) => {
const refMatch = /^({|\[[ \n\t\r]*)/.exec(context.data)
if (refMatch) {
return parseData(context)
}
const match = /^((".*?")|([0-9a-zA-Z]*))[ \n\t\r]*[,]?[ \n\t\r]*/.exec(context.data)
if (match) {
return parseValue(context, match)
}
throw Error('解析錯(cuò)誤')
}在上面定義狀態(tài)的時(shí)候,解析對(duì)象、數(shù)組和數(shù)組值的時(shí)候只有開(kāi)始狀態(tài),而沒(méi)有結(jié)束狀態(tài)。只是結(jié)束狀態(tài)統(tǒng)一放入 isEnd 函數(shù)中,。
解析流程
下面開(kāi)始定義 parseData 函數(shù):
第一步
const parseData = (ctx: { data: string }) => {
let res: any = ''
let currentState = State.INIT
while (isEnd(ctx, res)) {
switch (currentState) {
case State.INIT:
{
const match = /^[ \t\n\r]*/.exec(ctx.data)
if (match?.[0].length) {
advance(ctx, match[0].length)
}
if (ctx.data[0] === '{') {
res = {}
currentState = State.OBJECTSTART
} else if (ctx.data[0] === '[') {
res = []
currentState = State.ARRAYSTART
} else {
res = parseObjValue(ctx)
}
}
break
case State.OBJECTSTART:
break
case State.OBJVALSTART:
break
case State.OBJVALEND:
break
case State.ARRAYSTART:
break
case State.ARRVALSTART:
break
// no default
}
}
return res
}INIT 中,先去掉前面的空格、換行等字符,示例:
// 消費(fèi)前
const str1 = ' \t\n\r{"4":3,"s":"s","null":null,"arr":[1,"s",null],"obj":{}}'
// 消費(fèi)后
const str2 = '{"4":3,"s":"s","null":null,"arr":[1,"s",null],"obj":{}}'然后再判讀第一個(gè)字符是什么:
- 如果是
{,則將狀態(tài)轉(zhuǎn)移到OBJECTSTART,將res賦值一個(gè)空對(duì)象 - 如果是
[,則將狀態(tài)轉(zhuǎn)移到ARRAYSTART,將res賦值一個(gè)空數(shù)組 - 如果都不是,則就是一個(gè)值,可以用對(duì)象解析屬性值的方法來(lái)解析,判讀是否是合法的字符串
所以這里的狀態(tài)轉(zhuǎn)移到了對(duì)象解析 OBJECTSTART:
第二步
const parseData = (ctx: { data: string }) => {
let res: any = ''
let currentState = State.INIT
while (isEnd(ctx)) {
switch (currentState) {
case State.INIT:
// 省略部分代碼
break
case State.OBJECTSTART:
{
const match = /^{[ \t\n\r]*/.exec(ctx.data)
if (match) {
advance(ctx, match[0].length)
currentState = State.OBJVALSTART
}
}
break
case State.OBJVALSTART:
break
case State.OBJVALEND:
break
case State.ARRAYSTART:
break
case State.ARRVALSTART:
break
// no default
}
}
return res
}OBJECTSTART 中,消費(fèi)掉 {,將狀態(tài)轉(zhuǎn)移到 OBJVALSTART, 剩余字符數(shù)據(jù):
const str = '"4":3,"s":"s","null":null,"arr":[1,"s",null],"obj":{}}'第三步
const parseData = (ctx: { data: string }) => {
let res: any = ''
let currentState = State.INIT
while (isEnd(ctx)) {
switch (currentState) {
case State.INIT:
// 省略部分代碼
break
case State.OBJECTSTART:
// 省略部分代碼
break
case State.OBJVALSTART:
{
const match = /^"(.*?)"[ \n\t\r]*:[ \n\t\r]*/.exec(ctx.data)
if (match) {
advance(ctx, match[0].length)
if (ctx.data[0] === '{' || ctx.data[0] === '[') {
res[match[1]] = parseData(ctx)
} else {
res[match[1]] = parseObjValue(ctx)
}
currentState = State.OBJVALEND
}
}
break
case State.OBJVALEND:
break
case State.ARRAYSTART:
break
case State.ARRVALSTART:
break
// no default
}
}
return res
}先獲取 key: 等數(shù)組并消費(fèi),剩余字符數(shù)據(jù):
const str = '3,"s":"s","null":null,"arr":[1,"s",null],"obj":{}}'先判讀后續(xù)字符的第一個(gè)字符是什么:
- 如果是
{或者[,則開(kāi)啟一個(gè)新的狀態(tài)機(jī) - 否則直接用
parseObjValue解析值
最后將狀態(tài)轉(zhuǎn)移至 OBJVALEND。
第四步
const parseData = (ctx: { data: string }) => {
let res: any = ''
let currentState = State.INIT
while (isEnd(ctx)) {
switch (currentState) {
case State.INIT:
// 省略部分代碼
break
case State.OBJECTSTART:
// 省略部分代碼
break
case State.OBJVALSTART:
// 省略部分代碼
break
case State.OBJVALEND:
{
const match = /^[ \t\n\r]*(,)[ \t\n\r]*/.exec(ctx.data)
if (match) {
if (match[1] === ',') {
currentState = State.OBJVALSTART
}
advance(ctx, match[0].length)
}
}
break
case State.ARRAYSTART:
break
case State.ARRVALSTART:
break
// no default
}
}
return res
}如果后面匹配出來(lái)的字符是 ,,則表示后續(xù)還有其它的對(duì)象屬性,我們需要將狀態(tài)重新轉(zhuǎn)移到 OBJVALSTART, 如果是其它的 } 或者 ],則會(huì)在此次消費(fèi)完畢,然后在 isEnd 中會(huì)退出狀態(tài)機(jī)。
后續(xù)剩余字符的變化會(huì)依照上數(shù)狀態(tài)的變化而進(jìn)行字符消費(fèi):
const str = '3,"s":"s","null":null,"arr":[1,"s",null],"obj":{}}'
// 1
const str = ',"s":"s","null":null,"arr":[1,"s",null],"obj":{}}'
// 2
const str = '"s":"s","null":null,"arr":[1,"s",null],"obj":{}}'
// 省略 s 和 null
// 3 開(kāi)啟新的狀態(tài)機(jī)
const str = '[1,"s",null],"obj":{}}'
// 4 結(jié)束狀態(tài)機(jī)
const str = '],"obj":{}}'
// 5 開(kāi)啟新的狀態(tài)機(jī)
const str = '{}}'
// 6 結(jié)束狀態(tài)機(jī)
const str = '}}'
// 7 結(jié)束狀態(tài)機(jī)
const str = '}'數(shù)組的處理
const parseData = (ctx: { data: string }) => {
let res: any = ''
let currentState = State.INIT
while (isEnd(ctx)) {
switch (currentState) {
case State.INIT:
// 省略部分代碼
break
case State.OBJECTSTART:
// 省略部分代碼
break
case State.OBJVALSTART:
// 省略部分代碼
break
case State.OBJVALEND:
// 省略部分代碼
break
case State.ARRAYSTART:
{
const match = /^\[[ \t\n\r]*/.exec(ctx.data)
if (match) {
advance(ctx, match[0].length)
currentState = State.ARRVALSTART
}
}
break
case State.ARRVALSTART:
res.push(parseArrValue(ctx))
break
// no default
}
}
return res
}如果第一個(gè)字符為 [,則會(huì)開(kāi)啟新的狀態(tài)機(jī),狀態(tài)也會(huì)轉(zhuǎn)換為 ARRAYSTART,然后在 ARRAYSTART 狀態(tài)內(nèi)進(jìn)行數(shù)組值的轉(zhuǎn)換。
到這里整個(gè) JSON.parse 的實(shí)現(xiàn)思路差不多,但是上述的流程應(yīng)該有沒(méi)考慮到的地方,但是大體差不多,只是邊界的處理問(wèn)題。測(cè)試示例:
// 數(shù)據(jù)使用上面的 testJson const raws = JSON.stringify(testJson) const rawp = JSON.parse(raws) const cusp = parse(raws) console.log(raws, 'JSON.stringify') console.log(rawp, 'JSON.parse') console.log(cusp, 'parse')
結(jié)果:

完整代碼
const State = {
INIT: 'INIT',
OBJECTSTART: 'OBJECTSTART',
ARRAYSTART: 'ARRAYSTART',
OBJVALSTART: 'OBJVALSTART',
OBJVALEND: 'OBJVALEND',
ARRVALSTART: 'ARRVALSTART'
}
const isEnd = (ctx: { data: string }, res: any) => {
if (!ctx.data) {
return false
}
const match = /^([}\]])[ \t\n\r]*/.exec(ctx.data)
if (match) {
if (
match[1] === '}' && getType(res) !== 'Object' ||
match[1] === ']' && getType(res) !== 'Array'
) {
throw Error('解析錯(cuò)誤')
}
advance(ctx, match[0].length)
return false
}
return true
}
const advance = (context: { data: string }, num: number) => {
context.data = context.data.slice(num)
}
const parseValue = (context: { data: string }, match: any[]) => {
advance(context, match[0].length)
const valMatch = /^"(.*?)"$/.exec(match[1])
if (valMatch) {
return valMatch[1]
}
if (match[1] === 'null') {
return null
}
if (match[1] === 'true') {
return true
}
if (match[1] === 'false') {
return false
}
if (isNaN(+match[1])) {
throw Error('解析錯(cuò)誤')
}
return Number(match[1])
}
const parseObjValue = (context: { data: string }) => {
const match = /^[ \n\t\r]*((".*?")|([0-9A-Za-z]*))[ \t\n\r]*/.exec(context.data)
if (match) {
return parseValue(context, match)
}
new Error('解析錯(cuò)誤')
}
const parseArrValue = (context: { data: string }) => {
const refMatch = /^({|\[[ \n\t\r]*)/.exec(context.data)
if (refMatch) {
return parseData(context)
}
const match = /^((".*?")|([0-9a-zA-Z]*))[ \n\t\r]*[,]?[ \n\t\r]*/.exec(context.data)
if (match) {
return parseValue(context, match)
}
throw Error('解析錯(cuò)誤')
}
const parseData = (ctx: { data: string }) => {
let res: any = ''
let currentState = State.INIT
while (isEnd(ctx, res)) {
switch (currentState) {
case State.INIT:
{
const match = /^[ \t\n\r]*/.exec(ctx.data)
if (match?.[0].length) {
advance(ctx, match[0].length)
}
if (ctx.data[0] === '{') {
res = {}
currentState = State.OBJECTSTART
} else if (ctx.data[0] === '[') {
res = []
currentState = State.ARRAYSTART
} else {
res = parseObjValue(ctx)
}
}
break
case State.OBJECTSTART:
{
const match = /^{[ \t\n\r]*/.exec(ctx.data)
if (match) {
advance(ctx, match[0].length)
currentState = State.OBJVALSTART
}
}
break
case State.OBJVALSTART:
{
const match = /^"(.*?)"[ \n\t\r]*:[ \n\t\r]*/.exec(ctx.data)
if (match) {
advance(ctx, match[0].length)
if (ctx.data[0] === '{' || ctx.data[0] === '[') {
res[match[1]] = parseData(ctx)
} else {
res[match[1]] = parseObjValue(ctx)
}
currentState = State.OBJVALEND
}
}
break
case State.OBJVALEND:
{
const match = /^[ \t\n\r]*(,)[ \t\n\r]*/.exec(ctx.data)
if (match) {
if (match[1] === ',') {
currentState = State.OBJVALSTART
}
advance(ctx, match[0].length)
}
}
break
case State.ARRAYSTART:
{
const match = /^\[[ \t\n\r]*/.exec(ctx.data)
if (match) {
advance(ctx, match[0].length)
currentState = State.ARRVALSTART
}
}
break
case State.ARRVALSTART:
res.push(parseArrValue(ctx))
break
// no default
}
}
return res
}
export const parse = (data: string | number | null | boolean) => {
if (typeof data === 'number' || data === null || typeof data === 'boolean') {
return data
}
const context = { data }
return parseData(context)
}到此這篇關(guān)于JSON.stringify(遞歸)與 JSON.parse(有限狀態(tài)自動(dòng)機(jī))的實(shí)現(xiàn)代碼的文章就介紹到這了,更多相關(guān)JSON.stringify與 JSON.parse實(shí)現(xiàn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 詳解JSON.parse和JSON.stringify用法
- json.stringify()與json.parse()的區(qū)別以及用處
- 詳解關(guān)于JSON.parse()和JSON.stringify()的性能小測(cè)試
- JS使用JSON.parse(),JSON.stringify()實(shí)現(xiàn)對(duì)對(duì)象的深拷貝功能分析
- JavaScript 中 JSON.parse 函數(shù) 和 JSON.stringify 函數(shù)
- 關(guān)于JSON.parse(),JSON.stringify(),jQuery.parseJSON()的用法
相關(guān)文章
JS co 函數(shù)庫(kù)的含義和用法實(shí)例總結(jié)
這篇文章主要介紹了JS co 函數(shù)庫(kù)的含義和用法,結(jié)合實(shí)例形式總結(jié)分析了JS co 函數(shù)庫(kù)的基本含義、功能、用法及操作注意事項(xiàng),需要的朋友可以參考下2020-04-04
微信小程序 騰訊地圖SDK 獲取當(dāng)前地址實(shí)現(xiàn)解析
這篇文章主要介紹了微信小程序 騰訊地圖SDK 獲取當(dāng)前地址實(shí)現(xiàn)解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08
el-form實(shí)現(xiàn)表單和圖片手動(dòng)上傳和校驗(yàn)功能
在寫(xiě)項(xiàng)目時(shí),難免遇到需要上傳表單,圖片等文件,且表單內(nèi)容需進(jìn)行驗(yàn)證及必填項(xiàng)提示,圖片需要和信息一起傳遞且圖片載入后需可預(yù)覽,這篇文章給大家介紹el-form實(shí)現(xiàn)表單和圖片手動(dòng)上傳和校驗(yàn)功能,感興趣的朋友一起看看吧2024-01-01
Node+OCR實(shí)現(xiàn)圖像文字識(shí)別功能
這篇文章主要為大家詳細(xì)介紹了Node+OCR實(shí)現(xiàn)圖像文字識(shí)別功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-11-11
淺談一個(gè)webpack構(gòu)建速度優(yōu)化誤區(qū)
這篇文章主要介紹了淺談一個(gè)webpack構(gòu)建速度優(yōu)化誤區(qū),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-06-06
JavaScrip實(shí)現(xiàn)PHP print_r的數(shù)功能(三種方法)
PHP print_r的函數(shù)很好用,可以用來(lái)打印數(shù)組、對(duì)象等的結(jié)構(gòu)與數(shù)據(jù),可惜JavaScript并沒(méi)有原生提供類(lèi)似的函數(shù)。不過(guò)我們可以試著自己來(lái)實(shí)現(xiàn)這個(gè)函數(shù),下面提供一些方法與思路2013-11-11
js 聲明數(shù)組和向數(shù)組中添加對(duì)象變量的簡(jiǎn)單實(shí)例
下面小編就為大家?guī)?lái)一篇js 聲明數(shù)組和向數(shù)組中添加對(duì)象變量的簡(jiǎn)單實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-07-07

