热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

JSON.stringify与JSON.parse怎么实现

这篇“JSON.stringify与JSON.parse怎么实现”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借

这篇“JSON.stringify与JSON.parse怎么实现”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“JSON.stringify与JSON.parse怎么实现”文章吧。

JSON 的 stringifyparse 两个方法在平时的工作中也很常用,如果没有一些特殊的类型,是实现数据深拷贝的一个原生方式。

下面就这两个方法的一个手动实现思路。

JSON.stringify

JSON.stringify 方法用于将 Javascript 值转换为 JSON 字符串。该方法有三个参数:

  • data: 需要转换的数据

  • replacer:用于转换结果的对象或者数组,可以函数或者数组

  • space:文本添加缩进、空格和换行符,可以是数字或者字符串,数字的最大值是 10,字符串的最大长度是 10

下面的测试只用到这些类型: number,string,function,object,array,null,undefined,map,set,weakmap,weakset

但是 Javascript 数据的严格类型远远不止这几个。

data

首先我们用 JSON.stringify 来打印结果:

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":{}
// }

根据上面的结果,我们可以发现对象内的 function, undefined 被剔除了,map, set 等都被动的转换成了空对象。而数组内的 functionundefined 被替换成了 null

所以我们可以根据上述规则写一个简单的 stringify 方法:

const stringify = (data: any) => {
  // 获取数据的严格类型
  const type = getType(data)

  let res = ''
  switch (type) {
    case 'Object':
      // 处理对象
      res = stringifyObject(data)
      break
    case 'Array':
      // 处理数组
      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
}

实现几个辅助函数:

// 获取严格类型
const getType = (data: any) => {
  return Object.prototype.toString.call(data).slice(8, -1)
}

// 处理对象方法
const stringifyObject = (data: Record) => {
  const vals: string[] = []
  for (const key in data) {
    // 递归处理
    const val = stringify(data[key])
    // 如果值为 undefined,我们则需要跳过
    if (val !== undefined) {
      vals.push(`"${key}":${val}`)
    }
  }
  return `{${vals.join(',')}}`
}

// 处理数组方法
const stringifyArray = (data: any[]) => {
  const vals: any[] = []
  for (const val of data) {
    // 递归处理,如果返回 undefined 则替换为 null
    vals.push(stringify(val) || 'null')
  }
  return `[${vals.join(',')}]`
}

到这里就实现了 stringify 的简单版本。下面可以简单测试一下:

const raws = JSON.stringify(testJson)
const cuss = stringify(testJson)
console.log(raws === cuss) // true

后面还有两个参数,我们先实现第三个,第二个参数的作用等下在实现。

space

space 主要是用于添加空格、换行、缩进,但是只要 space 的值是合法的,换行符是默认加上一个的。所以我们要改下 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 === &#39;string&#39;) {
    space = space.substring(0, 10)
  } else if (space) {
    space = undefined
  }
  
  let res = &#39;&#39;
  switch (type) {
    case &#39;Object&#39;:
      res = stringifyObject(data, indent, replacer, space)
      break
    case &#39;Array&#39;:
      res = stringifyArray(data, indent, replacer, space)
      break
    // 省略部分代码
  }
  // 省略部分代码
}

对于 space 的不同非法的值,我们可以在控制台上进行一些简单的测试就可以得出,像 -1 这种其实是不生效的。而我处理的是只能是数字和字符串,数字必须是 1 - 10,字符串的最长长度是 10 位,其余的都重置为 undefined。因为像数组和对象的这种嵌套,缩进其实是要跟着动的,这里就新增了 indent 字段,初始为 1,后续递归就 + 1。

// 新增分隔符处理方法
const handleSeparator = (space: number | string, indent: number, prefix: string = &#39;&#39;, suffix: string = &#39;&#39;) => {
  let separator = prefix + &#39;\n&#39;

  if (typeof space === &#39;number&#39;) {
    separator += &#39; &#39;.repeat(space).repeat(indent)
  } else {
    separator += space.repeat(indent)
  }

  return separator + suffix
}

// 对象方法修改
const stringifyObject = (data: Record, 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 ? &#39; &#39; : &#39;&#39;}${val}`)
    }
  }

  // 新增 space 处理
  if (space) {
    const val = vals.join(handleSeparator(space, indent, &#39;,&#39;))
    if (!val) {
      return &#39;{}&#39;
    }
    const front = handleSeparator(space, indent, &#39;{&#39;)
    const back = handleSeparator(space, indent - 1, &#39;&#39;, &#39;}&#39;)
    return front + val + back
  }
  return `{${vals.join(&#39;,&#39;)}}`
}

// 数组处理方法
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) || &#39;null&#39;)
  }

  // 新增 space 处理
  if (space) {
    const front = handleSeparator(space, indent, &#39;[&#39;)
    const val = vals.join(handleSeparator(space, indent, &#39;,&#39;))
    const back = handleSeparator(space, indent - 1, &#39;&#39;, &#39;]&#39;)
    return front + val + back
  }
  return `[${vals.join(&#39;,&#39;)}]`
}

replacer

replacer 参数有两个类型:

  • 数组类型是用来过滤对象类型内的字段,只保留数组内的 key

  • 函数类型就是在数组和对象遍历的时候,开发者可以自定义某些类型的字符串方式

所以这里我们需要修改三处地方:

// 起始方法增加一个是否是第一次调用的标记 init
export const stringify = (data: any, replacer?: Replacer, space?: number | string, indent = 1, init = true) => {
  // 如果 replacer 为函数的话,初始 key 为空串,值为 data
  if (typeof replacer === &#39;function&#39; && init) {
    return stringify(replacer(&#39;&#39;, data), replacer, space, indent, false)
  }
  const type = getType(data)
  // 省略部分代码
}

const stringifyObject = (data: Record, indent: number, replacer?: Replacer, space?: number | string) => {
  const filter = getType(replacer) === &#39;Array&#39; ? replacer : null
  const vals: string[] = []
  for (const key in data) {
    // 补全参数,修改 replacer 处理
    const val = stringify(
      typeof replacer === &#39;function&#39; ? 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 ? &#39; &#39; : &#39;&#39;}${val}`)
    }
  }
  // 省略部分代码
}

const stringifyArray = (data: any[], indent: number, replacer?: Replacer, space?: number | string) => {
  const vals: any[] = []
  let i = 0
  for (const val of data) {
    // 补全参数,修改 replacer 处理
    vals.push(stringify(
      typeof replacer === &#39;function&#39; ? replacer(i++, val) : val,
      replacer,
      space,
      indent + 1,
      false
    ) || &#39;null&#39;)
  }

  if (space) {
    const front = handleSeparator(space, indent, &#39;[&#39;)
    const val = vals.join(handleSeparator(space, indent, &#39;,&#39;))
    const back = handleSeparator(space, indent - 1, &#39;&#39;, &#39;]&#39;)
    return front + val + back
  }
  return `[${vals.join(&#39;,&#39;)}]`
}

到这里, 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 = &#39;&#39;, suffix: string = &#39;&#39;) => {
  let separator = prefix + &#39;\n&#39;

  if (typeof space === &#39;number&#39;) {
    separator += &#39; &#39;.repeat(space).repeat(indent)
  } else {
    separator += space.repeat(indent)
  }

  return separator + suffix
}

const stringifyObject = (data: Record, indent: number, replacer?: Replacer, space?: number | string) => {
  const filter = getType(replacer) === &#39;Array&#39; ? replacer : null
  const vals: string[] = []
  for (const key in data) {
    const val = stringify(
      typeof replacer === &#39;function&#39; ? 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 ? &#39; &#39; : &#39;&#39;}${val}`)
    }
  }

  if (space) {
    const val = vals.join(handleSeparator(space, indent, &#39;,&#39;))
    if (!val) {
      return &#39;{}&#39;
    }
    const front = handleSeparator(space, indent, &#39;{&#39;)
    const back = handleSeparator(space, indent - 1, &#39;&#39;, &#39;}&#39;)
    return front + val + back
  }
  return `{${vals.join(&#39;,&#39;)}}`
}

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 === &#39;function&#39; ? replacer(i++, val) : val,
      replacer,
      space,
      indent + 1,
      false
    ) || &#39;null&#39;)
  }

  if (space) {
    const front = handleSeparator(space, indent, &#39;[&#39;)
    const val = vals.join(handleSeparator(space, indent, &#39;,&#39;))
    const back = handleSeparator(space, indent - 1, &#39;&#39;, &#39;]&#39;)
    return front + val + back
  }
  return `[${vals.join(&#39;,&#39;)}]`
}

export const stringify = (data: any, replacer?: Replacer, space?: number | string, indent = 1, init = true): string | undefined => {
  if (typeof replacer === &#39;function&#39; && init) {
    return stringify(replacer(&#39;&#39;, data), replacer, space, indent, false)
  }
  const type = getType(data)

  if (typeof space === &#39;number&#39;) {
    if (space <= 0) {
      space = undefined
    } else {
      space = Math.min(10, space)
    }
  } else if (typeof space === &#39;string&#39;) {
    space = space.substring(0, 10)
  } else if (space) {
    space = undefined
  }

  let res = &#39;&#39;
  switch (type) {
    case &#39;Object&#39;:
      res = stringifyObject(data, indent, replacer, space)
      break
    case &#39;Array&#39;:
      res = stringifyArray(data, indent, replacer, space)
      break
    case &#39;Number&#39;:
      res = `${data}`
      break
    case &#39;Boolean&#39;:
      res = `${data}`
      break
    case &#39;String&#39;:
      res = `"${data}"`
      break
    case &#39;Null&#39;:
      res = &#39;null&#39;
      break
    case &#39;Set&#39;:
    case &#39;WeakSet&#39;:
    case &#39;Map&#39;:
    case &#39;WeakMap&#39;:
      res = &#39;{}&#39;
      break
    default:
      return
  }
  return res
}

JSON.parse

stringify 方法的实现还是比较简单的,在一些笔试中还有可能会有相关需要实现的题。

JSON.parse 则是需要将合法的 json 字符串转换成对象,这里就需要用到一个概念:有限状态自动机

有限状态自动机

这里只做简单的介绍:有限状态机(Finite State Machine),是指任意时刻都处于有限状态集合中的某一状态。当其获得一个输入字符时,将从当前状态转换到另一个状态或者仍然保持当前状态。

可以结合当前 json 字符串的场景来简单理解一下:

我们有如下一个字符串:

const str = &#39;{"4":3,"s":"s","null":null,"arr":[1,"s",null],"obj":{}}&#39;

然后定义几个状态:

const State = {
  INIT: &#39;INIT&#39;,  // 初始状态
  OBJECTSTART: &#39;OBJECTSTART&#39;,  // 开始解析对象
  ARRAYSTART: &#39;ARRAYSTART&#39;,  // 开始解析数组
  OBJVALSTART: &#39;OBJVALSTART&#39;,  // 开始解析对象的属性与值
  OBJVALEND: &#39;OBJVALEND&#39;,  // 对象属性与值解析结束
  ARRVALSTART: &#39;ARRVALSTART&#39; // 开始解析数组值
}

因为 json 字符串是非常规则的字符串,所以我们可以结合正则表达式来提取相关步骤的数据,在字符串中的 &#39; &#39;\t\n\r 等也是可以的,所以在正则中需要考虑并且替换。

const parse = (data: string | number | null || boolean) => {
  if (typeof data === &#39;number&#39; || data === null || typeof data === &#39;boolean&#39;) {
    return data
  }
  // 将字符串转换为地址引用,方便后面字符串数据的消费
  const context = { data }
  // 具体解析方法
  return parseData(context)
}

然后定义几个辅助函数:

// 字符串的消费函数 - 就是截取已匹配完的数据,返回剩余字符串
const advance = (context: { data: string }, num: number) => {
  context.data = context.data.slice(num)
}

// 是否结束状态机
const isEnd = (ctx: { data: string }) => {
  // 如果没有数据了,则结束
  if (!ctx.data) {
    return false
  }
  const match = /^([}\]])[ \t\n\r]*/.exec(ctx.data)
  if (match) {
    if (
      match[1] === &#39;}&#39; && getType(res) !== &#39;Object&#39; ||
      match[1] === &#39;]&#39; && getType(res) !== &#39;Array&#39;
    ) {
      throw Error(&#39;解析错误&#39;)
    }
    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] === &#39;null&#39;) {
    return null
  }
  if (match[1] === &#39;true&#39;) {
    return true
  }
  if (match[1] === &#39;false&#39;) {
    return false
  }
  if (isNaN(+match[1])) {
    throw Error(&#39;解析错误&#39;)
  }
  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(&#39;解析错误&#39;)
}

// 解析数组值
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(&#39;解析错误&#39;)
}

在上面定义状态的时候,解析对象、数组和数组值的时候只有开始状态,而没有结束状态。只是结束状态统一放入 isEnd 函数中,。

解析流程

下面开始定义 parseData 函数:

第一步

const parseData = (ctx: { data: string }) => {
  let res: any = &#39;&#39;
  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] === &#39;{&#39;) {
            res = {}
            currentState = State.OBJECTSTART
          } else if (ctx.data[0] === &#39;[&#39;) {
            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 中,先去掉前面的空格、换行等字符,示例:

// 消费前
const str1 = &#39; \t\n\r{"4":3,"s":"s","null":null,"arr":[1,"s",null],"obj":{}}&#39;
// 消费后
const str2 = &#39;{"4":3,"s":"s","null":null,"arr":[1,"s",null],"obj":{}}&#39;

然后再判读第一个字符是什么:

  • 如果是 {,则将状态转移到 OBJECTSTART,将 res 赋值一个空对象

  • 如果是 [,则将状态转移到 ARRAYSTART,将 res 赋值一个空数组

  • 如果都不是,则就是一个值,可以用对象解析属性值的方法来解析,判读是否是合法的字符串

所以这里的状态转移到了对象解析 OBJECTSTART

第二步

const parseData = (ctx: { data: string }) => {
  let res: any = &#39;&#39;
  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 中,消费掉 {,将状态转移到 OBJVALSTART, 剩余字符数据:

const str = &#39;"4":3,"s":"s","null":null,"arr":[1,"s",null],"obj":{}}&#39;

第三步

const parseData = (ctx: { data: string }) => {
  let res: any = &#39;&#39;
  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] === &#39;{&#39; || ctx.data[0] === &#39;[&#39;) {
              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: 等数组并消费,剩余字符数据:

const str = &#39;3,"s":"s","null":null,"arr":[1,"s",null],"obj":{}}&#39;

先判读后续字符的第一个字符是什么:

  • 如果是 { 或者 [,则开启一个新的状态机

  • 否则直接用 parseObjValue 解析值

最后将状态转移至 OBJVALEND

第四步

const parseData = (ctx: { data: string }) => {
  let res: any = &#39;&#39;
  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] === &#39;,&#39;) {
              currentState = State.OBJVALSTART
            }
            advance(ctx, match[0].length)
          }
        }
        break
      case State.ARRAYSTART:
        break
      case State.ARRVALSTART:
        break
      // no default
    }
  }
  return res
}

如果后面匹配出来的字符是 ,,则表示后续还有其它的对象属性,我们需要将状态重新转移到 OBJVALSTART, 如果是其它的 } 或者 ],则会在此次消费完毕,然后在 isEnd 中会退出状态机。

后续剩余字符的变化会依照上数状态的变化而进行字符消费:

const str = &#39;3,"s":"s","null":null,"arr":[1,"s",null],"obj":{}}&#39;
// 1
const str = &#39;,"s":"s","null":null,"arr":[1,"s",null],"obj":{}}&#39;
// 2
const str = &#39;"s":"s","null":null,"arr":[1,"s",null],"obj":{}}&#39;
// 省略 s 和 null
// 3 开启新的状态机
const str = &#39;[1,"s",null],"obj":{}}&#39;
// 4 结束状态机
const str = &#39;],"obj":{}}&#39;
// 5 开启新的状态机
const str = &#39;{}}&#39;
// 6 结束状态机
const str = &#39;}}&#39;
// 7 结束状态机
const str = &#39;}&#39;

数组的处理

const parseData = (ctx: { data: string }) => {
  let res: any = &#39;&#39;
  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
}

如果第一个字符为 [,则会开启新的状态机,状态也会转换为 ARRAYSTART,然后在 ARRAYSTART 状态内进行数组值的转换。

到这里整个 JSON.parse 的实现思路差不多,但是上述的流程应该有没考虑到的地方,但是大体差不多,只是边界的处理问题。测试示例:

// 数据使用上面的 testJson
const raws = JSON.stringify(testJson)
const rawp = JSON.parse(raws)
const cusp = parse(raws)
console.log(raws, &#39;JSON.stringify&#39;)
console.log(rawp, &#39;JSON.parse&#39;)
console.log(cusp, &#39;parse&#39;)

结果:

JSON.stringify与JSON.parse怎么实现

完整代码

const State = {
  INIT: &#39;INIT&#39;,
  OBJECTSTART: &#39;OBJECTSTART&#39;,
  ARRAYSTART: &#39;ARRAYSTART&#39;,
  OBJVALSTART: &#39;OBJVALSTART&#39;,
  OBJVALEND: &#39;OBJVALEND&#39;,
  ARRVALSTART: &#39;ARRVALSTART&#39;
}
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] === &#39;}&#39; && getType(res) !== &#39;Object&#39; ||
      match[1] === &#39;]&#39; && getType(res) !== &#39;Array&#39;
    ) {
      throw Error(&#39;解析错误&#39;)
    }
    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] === &#39;null&#39;) {
    return null
  }
  if (match[1] === &#39;true&#39;) {
    return true
  }
  if (match[1] === &#39;false&#39;) {
    return false
  }
  if (isNaN(+match[1])) {
    throw Error(&#39;解析错误&#39;)
  }
  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(&#39;解析错误&#39;)
}

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(&#39;解析错误&#39;)
}

const parseData = (ctx: { data: string }) => {
  let res: any = &#39;&#39;
  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] === &#39;{&#39;) {
            res = {}
            currentState = State.OBJECTSTART
          } else if (ctx.data[0] === &#39;[&#39;) {
            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] === &#39;{&#39; || ctx.data[0] === &#39;[&#39;) {
              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] === &#39;,&#39;) {
              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 === &#39;number&#39; || data === null || typeof data === &#39;boolean&#39;) {
    return data
  }
  const context = { data }
  return parseData(context)
}

以上就是关于“JSON.stringify与JSON.parse怎么实现”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注编程笔记行业资讯频道。


推荐阅读
  • 在高并发需求的C++项目中,我们最初选择了JsonCpp进行JSON解析和序列化。然而,在处理大数据量时,JsonCpp频繁抛出异常,尤其是在多线程环境下问题更为突出。通过分析发现,旧版本的JsonCpp存在多线程安全性和性能瓶颈。经过评估,我们最终选择了RapidJSON作为替代方案,并实现了显著的性能提升。 ... [详细]
  • 本文介绍如何使用 Angular 6 的 HttpClient 模块来获取 HTTP 响应头,包括代码示例和常见问题的解决方案。 ... [详细]
  • 2018-2019学年第六周《Java数据结构与算法》学习总结
    本文总结了2018-2019学年第六周在《Java数据结构与算法》课程中的学习内容,重点介绍了非线性数据结构——树的相关知识及其应用。 ... [详细]
  • 深入解析Java枚举及其高级特性
    本文详细介绍了Java枚举的概念、语法、使用规则和应用场景,并探讨了其在实际编程中的高级应用。所有相关内容已收录于GitHub仓库[JavaLearningmanual](https://github.com/Ziphtracks/JavaLearningmanual),欢迎Star并持续关注。 ... [详细]
  • 本题来自WC2014,题目编号为BZOJ3435、洛谷P3920和UOJ55。该问题描述了一棵不断生长的带权树及其节点上小精灵之间的友谊关系,要求实时计算每次新增节点后树上所有可能的朋友对数。 ... [详细]
  • 本文介绍了如何在 Node.js 中使用 `setDefaultEncoding` 方法为可写流设置默认编码,并提供了详细的语法说明和示例代码。 ... [详细]
  • 本文介绍如何从字符串中移除大写、小写、特殊、数字和非数字字符,并提供了多种编程语言的实现示例。 ... [详细]
  • 深入解析Java虚拟机(JVM)架构与原理
    本文旨在为读者提供对Java虚拟机(JVM)的全面理解,涵盖其主要组成部分、工作原理及其在不同平台上的实现。通过详细探讨JVM的结构和内部机制,帮助开发者更好地掌握Java编程的核心技术。 ... [详细]
  • 黑马头条项目:Vue 文章详情模块与交互功能实现
    本文详细介绍了如何在黑马头条项目中配置文章详情模块的路由、获取和展示文章详情数据,以及实现关注、点赞、不喜欢和评论功能。通过这些步骤,您可以全面了解如何开发一个完整的前端文章详情页面。 ... [详细]
  • 深入解析动态代理模式:23种设计模式之三
    在设计模式中,动态代理模式是应用最为广泛的一种代理模式。它允许我们在运行时动态创建代理对象,并在调用方法时进行增强处理。本文将详细介绍动态代理的实现机制及其应用场景。 ... [详细]
  • Qt QTableView 内嵌控件的实现方法
    本文详细介绍了在 Qt QTableView 中嵌入控件的多种方法,包括使用 QItemDelegate、setIndexWidget 和 setIndexWidget 结合布局管理器。每种方法都有其适用场景和优缺点。 ... [详细]
  • JavaScript 中创建对象的多种方法
    本文详细介绍了 JavaScript 中创建对象的几种常见方式,包括对象字面量、构造函数和 Object.create 方法,并提供了示例代码和属性描述符的解释。 ... [详细]
  • 云函数与数据库API实现增删查改的对比
    本文将深入探讨使用云函数和数据库API实现数据操作(增删查改)的不同方法,通过详细的代码示例帮助读者更好地理解和掌握这些技术。文章不仅提供代码实现,还解释了每种方法的特点和适用场景。 ... [详细]
  • 深入解析SpringMVC核心组件:DispatcherServlet的工作原理
    本文详细探讨了SpringMVC的核心组件——DispatcherServlet的运作机制,旨在帮助有一定Java和Spring基础的开发人员理解HTTP请求是如何被映射到Controller并执行的。文章将解答以下问题:1. HTTP请求如何映射到Controller;2. Controller是如何被执行的。 ... [详细]
  • 本文详细解释了为什么在成功执行移动赋值操作后,对象的析构函数会被调用,并提供了代码示例和详细的分析。 ... [详细]
author-avatar
R-hehe
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有