2

什么是 JSON

JSON(JavaScript Object Notation)是一种轻量的数据格式,它不是一门编程语言。JSON是基于JavaScript Programming Language,Standard ECMA-262 3rd Edition - December 1999的一个子集。但 JSON 并不属于 JavaScript,很多编程语言都有针对 JSON 的解析器和序列化器。

JSON 语法

根据红宝书中的介绍,JSON 有三种类型的值,分别为简单值、对象和数组。

  • 简单值:使用与 JavaScript 相同的语法,可以在 JSON 中表示字符串、数值、布尔值和 null。但 JSON 不支持 JavaScript 中的特殊值 undefined
  • 对象:对象作为一种复杂数据类型,表示的是一组无序的键值对儿。而每个键值对儿中的值可 以是简单值,也可以是复杂数据类型的值。
  • 数组:数组也是一种复杂数据类型,表示一组有序的值的列表,可以通过数值索引来访问其中 的值。数组的值也可以是任意类型——简单值、对象或数组。

在 JavaScript 中已经内置了 JSON 对象,它有 parsestringify 两个方法。

image.png

在实际工作中,这两个方法我们也经常用到,例如实现对象深拷贝时

const obj = {
  name: 'Jay',
  age: 41
}
const jsonStr =  JSON.stringify(obj) // {"name":"Jay","age":41}
const copiedObj = JSON.parse(jsonStr) // {name: "Jay", age: 41}

本文就来实现 JSONparse 方法。

实现 parse

我们先看一下 JSON 字符串的结构

const json = `
{ 
  "status": 100,
  "msg": "返回成功",
  "data": { 
    "string": "abc",
    "array": [1,2,3], 
    "children": [ 
      { "name": "Jay", "age": 41, "occupation": "Musician"}, 
      { "name": "Jack", "age": 56, "occupation": "CEO"}, 
      { "name": "Kobe", "age": 42, "occupation": "Basketball players"}
    ]
  } 
}
`;

我们的函数就叫做 fakeParseJSON,我们先用原生方法跑一下

const fakeParseJSON = JSON.parse
fakeParseJSON(json) // {status: 100, msg: "返回成功", data: {…}}

我们先从简单值开始来写

parseValue

值(value)可以是双引号括起来的字符串(string)、数值(number)、truefalsenull、对象(object)或者数组(array)。这些结构可以嵌套。

流程图如下:

以值为 string 类型为例

const str = `
  "hello world"
`

上面就是一个简单的 JSON 值(value),根据流程图,从左往右会经过 whitespace, ", string, ", whitespace。我们就一个一个来处理。

function fakeParseJSON(str) {
  let i = 0
  
  // 处理 whitespace, 遇到空格,回车,制表符等直接跳过
  function parseWhiteSpace() {
    while(str[i] === ' ' || str[i] === '\n' || str[i] === '\r' || str[i] === '\t') {
      i++
    }
  }
  
  function parseValue() {
    // 首先处理前面可能有的空格
    parseWhiteSpace()
    // 处理 string
    if(str[i] === '"') { // 以双引号开头
      i++
      let res = ''
      while(str[i] !== '"') {
        res += str[i]
        i++
      }
      // 继续往下移
      i++
      return res
    }
  }
}

测试一下

fakeParseJSON(str) // hello world

我们的 JSON 值的类型不仅有 string,还有 number, object 等类型。我们最后要处理的都是 JSONvalue,而且我们知道 valueobject 类型是“名称/值”对的集合形式,名称一般都是字符串,值的话各种类型都有。在解析 JSON 对象时,我们要处理名称,这里我们先单独抽离一个专门处理字符串的函数 parseString,我们改动一下代码

function fakeParseJSON(str) {
  let i = 0
  
  // 处理 whitespace,
  // ...
  
  // 处理字符串
  function parseString() {
    parseWhiteSpace()
    if(str[i] === '"') { // 以双引号开头
      i++
      let res = ''
      while(str[i] !== '"') {
        res += str[i]
        i++
      }
      // 继续往下移
      i++
      return res
    }
  }
  
  // 处理结果
  function parseValue() {
     return parseString()
  }
  // 输出结果
  return parseValue()
}

parseObject

接下来我们来处理 JSON 对象,先看看流程图

从图中我们可以看出是以"{" 开头,"}"结尾,中间可能会经历 whitespace, string, :, whitespace, value, ,, ...。我们也先以值为简单类型的为例

const obj = `
{
  "msg": "返回成功"
}
`

我们也加上一个 parseObject 的函数

function fakeParseJSON(str) {
  let i = 0
  
  // 处理 whitespace
  // ...
  
  // 处理冒号
  function parseColon() {
    if(str[i] !== ":") {
      throw new Error('Expected ":".')
    }
    i++
  }
  
  // 处理字符串
  // ...
  
  // 处理对象
  function parseObject() {
    parseWhiteSpace()
    if(str[i] === '{') {
      i++
      parseWhiteSpace()
      const result = {}
      while(str[i] !== '}') { 
        // 处理字符串
        const key = parseString()
        parseWhiteSpace()
        parseColon() // 这里新加一个处理冒号的
        const value = parseValue()
        result[key] = value
      }
      i++
      return result
    }
  }

  function parseValue() {
    parseWhiteSpace()
    const value = 
      parseString() ||
      parseObject()
      
    parseWhiteSpace()
    return value
  }
  return parseValue()
}

测试一下

fakeParseJSON(obj) // {msg: "返回成功"}

parseArray

我们继续丰富一下,在之前的 json 对象基础上增加数组类型

const obj = `
{
  "msg": "返回成功",
  "arr": ["a","b","c"]
}
`;

这里我们有两个任务要处理,其一是处理新增的 , ,还有就是处理数组。

function fakeParseJSON(str) {
  let i = 0
  // ...
  // 处理逗号
  function parseComma() {
    if (str[i] !== ",") {
      throw new Error('Expected ",".');
    }
    i++;
  }
    
  // 处理对象
  function parseObject() {
    parseWhiteSpace()
    if(str[i] === '{') {
      // ...
      let initial = true
      while(str[i] !== '}') { 
        if(!initial) {
          parseComma() // 处理逗号
          parseWhiteSpace()
        }
        // ...
        initial = false
      }
      // ...
    }
  }
  // ...
}

其二是处理数组,我们看看数组的流程图

和处理对象类型差不多,直接上代码

function fakeParseJSON(str) {
  let i = 0
  // ...
  // 处理数组
  function parseArray() {
    if(str[i] === "[") {
      i++
      parseWhiteSpace()
      
      const result = []
      let initial = true
      while(str[i] !== "]") {
        if(!initial) {
          parseComma()
          parseWhiteSpace()
        }
        const value = parseValue()
        result.push(value)
        initial = false
      }
      i++
      return result
    }
  }
  
  function parseValue() {
    parseWhiteSpace()
    const value = 
      parseString() ||
      parseObject() ||
      parseArray()
      
    parseWhiteSpace()
    return value
  }
  return parseValue()
}

parseNumber

处理 number 情况比较麻烦,我们也是先看一下流程图

从图中可以看出,需要处理负数,小数以及指数等情况

function fakeParseJSON(str) {
  let i = 0
  // ...
  // 处理 number
  function parseNumber() {
    let start = i
    if (str[i] === "-") i++
    if (str[i] === "0") {
      i++
    } else if (str[i] >= "1" && str[i] <= "9") {
      i++
      while (str[i] >= "0" && str[i] <= "9") {
        i++;
      }
    }

    if (str[i] === ".") {
      i++
      while (str[i] >= "0" && str[i] <= "9") {
        i++
      }
    }
    if (str[i] === "e" || str[i] === "E") {
      i++
      if (str[i] === "-" || str[i] === "+") {
        i++
      }
      while (str[i] >= "0" && str[i] <= "9") {
        i++
      }
    }
    if (i > start) {
      return Number(str.slice(start, i));
    }
  }
  
  function parseValue() {
    parseWhiteSpace()
    const value = 
      parseString() ||
      parseObject() ||
      parseArray() ||
      parseNumber()

    parseWhiteSpace()
    return value
  }
  // 输出结果
  return parseValue()
}

其他情况

我们还差布尔值以及 null 的情况

function fakeParseJSON(str) {
  let i = 0
  // ...
  // true, false and null
  function parseKeyword(name, value) {
    if (str.slice(i, i + name.length) === name) {
      i += name.length;
      return value;
    }
  }
  function parseValue() {
    parseWhiteSpace()
    const value = 
      parseString() ||
      parseObject() ||
      parseArray() ||
      parseNumber() ||
      parseKeyword("true", true) ||
      parseKeyword("false", false) ||
      parseKeyword("null", null)

    parseWhiteSpace()
    return value
  }
  // 输出结果
  return parseValue()
}

结语

至此,我们大概实现了一个 JSON.parse 方法,当然还很不完善,比如字符串的处理以及容错处理。

字符串(_string_)是由双引号包围的任意数量Unicode字符的集合,使用反斜线转义。一个字符(character)即一个单独的字符串(character string)。


见贤思齐
66 声望8 粉丝

写代码的