AnnVoV / blog

24 stars 2 forks source link

自己来实现JSON.stringify和JSON.parse #30

Open AnnVoV opened 5 years ago

AnnVoV commented 5 years ago

JSON.stringify 不能处理哪些类型

阅读资料: What you didn’t know about JSON.Stringify https://abdulapopoola.com/2017/02/27/what-you-didnt-know-about-json-stringify/

最主要的原因在于:

Because JSON is a language agnostic format JSON 是通用的文本格式和语言是无关的。如果函数定义也可以stringify的话,那就变得和语言有关了

JSON.stringify

const getType = (obj) => {
    return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
};

const getResultFromStack = (stack) => {
    let result = '';
    while (stack.length) {
        const [key, value] = stack.shift();
        const temp = (stack.length === 0) ?
            `"${key}":${value}` : `"${key}":${value},`;
        result += temp;
    }
    return `{${result}}`;
};

//『function|undefined』
const isFunctionOrUndefined = (obj) => /function|undefined/.test(getType(obj));

export default function jsonStringify(obj) {
    if (obj === null)
        return 'null';

    if (getType(obj) === 'regexp')
        return '{}';

    //『字符串|布尔|数字』直接转换为string类型
    if (/string|number|boolean/.test(typeof obj)) {
        return (typeof obj === 'number') ? obj.toString() : `"${obj.toString()}"`;
    }

    //『function|undefined』 不处理
    if (isFunctionOrUndefined(obj))
        return '';

    // 『数组』直接join 为字符串
    if (Array.isArray(obj))
        return `[${obj.join(',')}]`;

    if (getType(obj) === 'object') {
        const stack = [];
        // 『对象』递归处理
        for (const key in obj) {
            const value = obj[key];
            if (isFunctionOrUndefined(value)) {
                continue;
            }
            stack.push([key, jsonStringify(value)]);
        }
        return getResultFromStack(stack);
    }
}

递归处理那里,我们其实只要能把key,value 同时存放到队列里,稍后处理就可以了,使用二维数组比较方便

JSON.parse

1.最简单的实现方式, 无脑实现利用new Function

function jsonParseFunction(str) {
    return new Function(`return ${str}`)();
}

2.写一个简单的针对Json的parser

思路来源于下面这篇文章,非常感谢作者: 半小时实现一个JSON 解析器 https://zhuanlan.zhihu.com/p/28049617

并且文章中解释了为什么JSON要设计成这个样子?因为它是容易解析的。读取JSON字符串的字符时,读到特定的开头,我们就能知道我们需要找的是什么值。

let str = '';
let i = 0;

const parseObject = () => {
    i++;
    const result = {};
    while (str[i] !== '}') {
        // 这个地方一开始没绕过来
        const key = parseString();

        i++; // 跳过冒号
        const value = parseValue();
        result[key] = value;
        if (str[i] === ',') {
            i++; // 如果是, 说明要继续往下查找
        }
    }
    i++;
    return result;
};

const parseArray = () => {
    i++;
    let result = [];
    while (str[i] !== ']') {
        result.push(parseValue());
        if (str[i] === ',') {
            // 跳过逗号
            i++;
        }
    }
    i++;
    return result;
};

const parseString = () => {
    i++;
    let result = '';
    while (str[i] !== '"') {
        result += str[i];
        i++;
    }
    i++;
    return result;
};

const parseTrue = () => {
    const content = str.substr(i, 4);
    if (content === 'true') {
        i += 4;
        return true;
    }
};

const parseFalse = () => {
    const content = str.substr(i, 5);
    if (content === 'false') {
        i += 5;
        return false;
    }

};

const parseNull = () => {
    const content = str.substr(i, 4);
    if (content === 'null') {
        i += 4;
        return null;
    }
};

const parseNumber = () => {
    function isNumberChar(c) {
        var chars = {
            '-': true,
            '+': true,
            'e': true,
            'E': true,
            '.': true,
        };
        if (chars[c]) {
            return true;
        }
        if (c >= '0' && c <= '9') {
            return true;
        }
        return false;
    }

    let numStr = '';
    while (isNumberChar(str[i])) {
        numStr += str[i++];
    }
    return parseFloat(numStr);
};

const parseValue = () => {

    switch (str[i]) {
        case '{': {
            return parseObject();
        }
        case '[': {
            return parseArray();
        }
        case '"': {
            return parseString();
        }
        case "t": {
            return parseTrue();
        }
        case "f": {
            return parseFalse();
        }
        case "n": {
            return parseNull();
        }
        default: {
            return parseNumber();
        }
    }
};

export default function jsonParse(orgStr) {
    str = orgStr.replace(/([^"])(\s*)/g, '$1');
    const result = parseValue();
    console.log(result);
}

// --- 测试 ---
import jsonParse from './jsonParse';
let str = '{"a":1, "b":true, "c":false, "foo":null, "bar":[1,2,3]}';
jsonParse(str);