render函数的基本实现
js中序列化就是对象转换成json格式的字符串,使用JSON对象的stringify方法,接收一个javaScript对象,返回一个json字符串。主要使用场景用于数据传输和对象全等对比。
const person = {
name: "John",
age: 30,
test: {
tips: '我叫王大锤',
site: 'http://www.example.com',
screen: null
},
city: "New York",
};
JSON.stringify(person)
反序列化使用JSON对象的parse方法,接收一个json字符串。返回javaScript对象。主要使用场景,处理接口返回的数据,缓存数据处理。
JSON.parse(JSON.stringify(person))
本文主要讲述该功能的实现及序列化原理和反序列化
观察json数据的格式, 发现结构和javaScript的对象结构一样,只是整体都是字符串。原本的对象键都是用双引号包裹,值如果是字符串类型是也是双引号包裹,如果不是则是没有引号包裹。
数字和null值都没有被引号包裹。所以只有值是字符串的时候才需要被双引号包裹,其他类型直接转字符串。通过json字符串的规律,可以把对象对象变成json字符串。
class CustomJson {
/**
* 序列化函数
* @param {object} param
* @returns {string}
*/
stringify(param) {
// 判断是否对象如果不是或不可转换成序列化字符串,抛出错误
if (typeof param === 'object' && param !== null) {
// 序列化回调函数
const stringify = (p) => {
const result = []
// 分三种类型, 对象数组, 和非数组和对象或null类型
// 使用栈的特性保存处理后的字符串, 也可以使用字符串拼接
// 要手动拼接逗号
// 字符串数据用双引号包裹再转成字符串, 其他基本类型数据直接转字符串
if (Array.isArray(p)) {
result.push('[')
p.forEach((item, index) => {
result.push(`${stringify(item)}${index !== p.length - 1 ? ',' : ''}`)
})
result.push(']')
} else if (typeof p === 'object' && p !== null) {
result.push('{')
const len = Object.keys(p).length
Object.keys(p).forEach((key, index) => {
result.push(`"${key}":${stringify(p[key])}${(len - 1 !== index) ? ',' : ''}`)
})
result.push('}')
} else {
if (typeof p === 'string') {
result.push(`"${p}"`)
} else {
result.push(`${p}`)
}
}
// 最后将数组转换成字符串
return result.join('')
}
return stringify(param)
} else {
throw new Error('param is not object')
}
}
序列化验证一下
const person = {
name: "John",
age: 30,
test: {
tips: '我叫王大锤',
site: 'http://www.example.com',
screen: null,
test: {
tips: '我叫王大锤',
site: 'http://www.example.com',
screen: null,
test: {
tips: '我叫王大锤',
site: 'http://www.example.com',
screen: null,
}
}
},
city: "New York",
list: [
'1',
2,
3,
{
a: [
{
b: [1,2,3,4,5]
}
]
}
]
};
const test2 = [
'1',
2,
3,
{
a: [
{
b: [1,2,3,4,5]
}
]
}
]
const explamJSon = new CustomJson()
const testStr = explamJSon.stringify(person)
console.log('testStr 序列化测试对象', testStr)
console.log('JSON.parse 使用原生反序列化', JSON.parse(testStr))
const testStr2 = explamJSon.stringify(test2)
console.log('testStr2 序列化测试对象', testStr2)
console.log('JSON.parse 使用原生反序列化', JSON.parse(testStr2))
可以正常转换成json字符串
为了验证格式的正确性,使用原生的JSON.parse反序列化转换成对象
const explamJSon = new CustomJson()
const testStr = explamJSon.stringify(person)
console.log(testStr)
console.log(JSON.parse(testStr))
测试结果,使用自定义序列化函数序列化的结果可以被原生的JSON.parse函数反序列化。验证成功。
顾名思义反序列化与序列化的作用相反,序列化的时候我们将js对象转换成了json字符串。现在将json字符串转换成js对象。这个方法比序列化要复杂一些,序列化的时候对象本身格式是固定的。有特定的方法来处理。反序列化本身是要处理字符串,通过两分半的观察,json格式的字符串也是有规律的。每个属性之间的逗号可以作为分割符号来拆解字符串。拆解到最小颗粒度将值还原,最后返回结果。
class CustomJson {
/**
* 反序列化
* @param {string} jsonData
* @returns {object}
*/
parse (jsonData) {
/**
* 查询索引, 因为json之中使用英文逗号作为分割每一项, 使用栈先出,后进后出的特性, 获取,的坐标. 用数组存储返回
* @param {string} str
* @returns {array}
*/
const queryIdx = (str) => {
const list = []
let tempArr = []
for (let i = 0; i < str.length; i += 1) {
if (str[i] === '{' || str[i] === '[') {
tempArr.push(str[i])
}
if (tempArr[tempArr.length - 1] === '{' && str[i] === '}') {
tempArr.pop()
}
if (tempArr[tempArr.length - 1] === '[' && str[i] === ']') {
tempArr.pop()
}
if (str[i] === ',' && tempArr.length === 0) {
list.push(i)
}
}
return list
}
/**
* 还原数组, 在拿到分割坐标的基础之上, 将json字负串分解成字符串片段, 用数组存储返回
* @param {string} val
* @returns {array}
*/
const splitObject = (val) => {
const s = val.slice(1, val.length - 1)
const idxList = queryIdx(s)
const tempArr = []
let silceIdx = 0
idxList.forEach(idx => {
tempArr.push(s.slice(silceIdx, idx))
silceIdx = idx + 1
})
tempArr.push(s.slice(silceIdx))
return tempArr
}
/**
* 分解字符串, 分别处理数组和对象
* @param {string} str
* @returns {object}
*/
const decomposeStr = (str) => {
/**
* 判断是不是一个合法的json字符串, 如果不是抛出错误
*/
if (jsonData[0] === '{' && jsonData[jsonData.length - 1] !== '}' || jsonData[0] === '[' && jsonData[jsonData.length - 1] !== ']') {
throw new Error('jsonData is not json string')
} else if (jsonData[0] != '{' || jsonData[0] === '[') {
throw new Error('jsonData is not json string')
}
const result = str[0] === '[' ? [] : {}
if (Array.isArray(result)) {
const arr = splitObject(str)
arr.forEach(item => {
if (item.trim()[0] === '{' && item.trim()[item.trim().length - 1] === '}' || item.trim()[0] === '[' && item.trim()[item.trim().length - 1] === ']'){
result.push(decomposeStr(item.trim()))
} else {
result.push(convertVlaue(item.trim()))
}
})
} else {
const arr = splitObject(str)
arr.forEach(item => {
if (item.trim()[0] === '{' && item.trim()[item.trim().length - 1] === '}' || item.trim()[0] === '[' && item.trim()[item.trim().length - 1] === ']'){
result.push(decomposeStr(item.trim()))
} else {
const keyValues = item.trim()
const divideIdx = keyValues.indexOf(':')
const key = keyValues.slice(0, divideIdx)
const value = keyValues.slice(divideIdx + 1)
result[key.trim().slice(1, key.length - 1)] = convertVlaue(value)
}
})
}
return result
}
/**
* 转换值, 根据之前不同的类型,将值还原,
* 在序列化的时候, 因为特殊处理了字符串数据,发现是有双引号包裹的去掉双引号.
* 没有双引号包裹的特殊值,特殊处理
* @param {string} val
* @returns {any}
*/
const convertVlaue = (val) => {
if (val[0] === '"' && val[val.length - 1] === '"') {
return val.slice(1, val.length - 1)
} else if (val === 'null') {
return null
} else if (val === 'true') {
return true
} else if (val === 'false') {
return false
} else if (val === 'NaN') {
return NaN
} else if (Number(val) === 0 || Number(val)) {
return Number(val)
} else if (val[0] === '{' && val[val.length - 1] === '}' || val[0] === '[' && val[val.length - 1] === ']') {
return decomposeStr(val)
} else {
return val.trim()
}
}
return decomposeStr(jsonData)
}
}
测试
console.log('使用自定义反序列化', explamJSon.parse(testStr))
console.log('原生JSON.stringify序列化自定义反序列化结果', JSON.parse(testStr))
const explamJSon = new CustomJson()
// 非合法json字符串
console.log('异常判断', explamJSon.parse('1111'))
// 非合法对象
console.log('异常判断', explamJSon.stringify('1111'))
总的来说,javaScript对的序列化过程是对象的解析,以字符串形式存储对象结构。反序列化是解析字符串,还原成对象。理解这个过程,有助于我们解决复杂问题。