zoukankan      html  css  js  c++  java
  • 20.2 解析与序列化【JavaScript高级程序设计第三版】

    JSON 之所以流行,拥有与JavaScript 类似的语法并不是全部原因。更重要的一个原因是,可以把JSON 数据结构解析为有用的JavaScript 对象。与XML 数据结构要解析成DOM 文档而且从中提取数据极为麻烦相比,JSON 可以解析为JavaScript 对象的优势极其明显。就以上一节中包含一组图书的JSON数据结构为例,在解析为JavaScript 对象后,只需要下面一行简单的代码就可以取得第三本书的书名:

    books[2].title

    当然,这里是假设把解析JSON 数据结构后得到的对象保存到了变量books 中。再看看下面在DOM结构中查找数据的代码:

    doc.getElementsByTagName("book")[2].getAttribute("title")

    看看这些多余的方法调用,就不难理解为什么JSON 能得到JavaScript 开发人员的热烈欢迎了。从此以后,JSON 就成了Web 服务开发中交换数据的事实标准。

    20.2.1 JSON对象

    早期的JSON 解析器基本上就是使用JavaScript 的eval()函数。由于JSON 是JavaScript 语法的子集,因此eval()函数可以解析、解释并返回JavaScript 对象和数组。ECMAScript 5 对解析JSON 的行为进行规范,定义了全局对象JSON。支持这个对象的浏览器有IE 8+、Firefox 3.5+、Safari 4+、Chrome和Opera 10.5+。对于较早版本的浏览器,可以使用一个shim:https://github.com/douglascrockford/JSON-js。在旧版本的浏览器中,使用eval()对JSON 数据结构求值存在风险,因为可能会执行一些恶意代码。对于不能原生支持JSON 解析的浏览器,使用这个shim 是最佳选择。

    JSON 对象有两个方法:stringify()和parse()。在最简单的情况下,这两个方法分别用于把JavaScript 对象序列化为JSON 字符串和把JSON 字符串解析为原生JavaScript 值。例如:

    var book = {
    	title: "Professional JavaScript",
    	authors: ["Nicholas C. Zakas"],
    	edition: 3,
    	year: 2011
    };
    var jsonText = JSON.stringify(book);

    运行一下
    这个例子使用JSON.stringify()把一个JavaScript 对象序列化为一个JSON 字符串,然后将它保存在变量jsonText 中。默认情况下,JSON.stringify()输出的JSON 字符串不包含任何空格字符或缩进,因此保存在jsonText 中的字符串如下所示:

    {
    	"title": "Professional JavaScript",
    	"authors": ["Nicholas C. Zakas"],
    	"edition": 3,
    	"year": 2011
    }

    在序列化JavaScript 对象时,所有函数及原型成员都会被有意忽略,不体现在结果中。此外,值为undefined 的任何属性也都会被跳过。结果中最终都是值为有效JSON 数据类型的实例属性。
    将JSON 字符串直接传递给JSON.parse()就可以得到相应的JavaScript 值。例如,使用下列代码就可以创建与book 类似的对象:

    var bookCopy = JSON.parse(jsonText);

    注意,虽然book 与bookCopy 具有相同的属性,但它们是两个独立的、没有任何关系的对象。
    如果传给JSON.parse()的字符串不是有效的JSON,该方法会抛出错误。

    20.2.2 序列化选项

    实际上,JSON.stringify()除了要序列化的JavaScript 对象外,还可以接收另外两个参数,这两个参数用于指定以不同的方式序列化JavaScript 对象。第一个参数是个过滤器,可以是一个数组,也可以是一个函数;第二个参数是一个选项,表示是否在JSON 字符串中保留缩进。单独或组合使用这两个参数,可以更全面深入地控制JSON 的序列化。

    1. 过滤结果

    如果过滤器参数是数组,那么JSON.stringify()的结果中将只包含数组中列出的属性。来看下面的例子。

    var book = {
    	"title": "Professional JavaScript",
    	"authors": ["Nicholas C. Zakas"],
    	edition: 3,
    	year: 2011
    };
    var jsonText = JSON.stringify(book, ["title", "edition"]);

    运行一下
    JSON.stringify()的第二个参数是一个数组,其中包含两个字符串:"title"和"edition"。这两个属性与将要序列化的对象中的属性是对应的,因此在返回的结果字符串中,就只会包含这两个属性:

    {"title":"Professional JavaScript","edition":3}

    如果第二个参数是函数,行为会稍有不同。传入的函数接收两个参数,属性(键)名和属性值。根据属性(键)名可以知道应该如何处理要序列化的对象中的属性。属性名只能是字符串,而在值并非键值对儿结构的值时,键名可以是空字符串。
    为了改变序列化对象的结果,函数返回的值就是相应键的值。不过要注意,如果函数返回了undefined,那么相应的属性会被忽略。还是看一个例子吧。

    var book = {
    	"title": "Professional JavaScript",
    	"authors": ["Nicholas C. Zakas"],
    	edition: 3,
    	year: 2011
    };
    var jsonText = JSON.stringify(book,
    function(key, value) {
    	switch (key) {
    	case "authors":
    		return value.join(",")
    	case "year":
    		return 5000;
    	case "edition":
    		return undefined;
    	default:
    		return value;
    	}
    });

    运行一下
    这里,函数过滤器根据传入的键来决定结果。如果键为"authors",就将数组连接为一个字符串;如果键为"year",则将其值设置为5000;如果键为"edition",通过返回undefined 删除该属性。
    最后,一定要提供default 项,此时返回传入的值,以便其他值都能正常出现在结果中。实际上,第一次调用这个函数过滤器,传入的键是一个空字符串,而值就是book 对象。序列化后的JSON 字符串如下所示:

    {"title":"Professional JavaScript","authors":"Nicholas C. Zakas","year":5000}

    要序列化的对象中的每一个对象都要经过过滤器,因此数组中的每个带有这些属性的对象经过过滤之后,每个对象都只会包含"title"、"authors"和"year"属性。

    Firefox 3.5 和3.6 对JSON.stringify()的实现有一个bug,在将函数作为该方法的第二个参数时这个bug 就会出现,即这个函数只能作为过滤器:返回undefined 意味着要跳过某个属性,而返回其他任何值都会在结果中包含相应的属性。Firefox 4 修复了这个bug。

    2. 字符串缩进

    JSON.stringify()方法的第三个参数用于控制结果中的缩进和空白符。如果这个参数是一个数值,那它表示的是每个级别缩进的空格数。例如,要在每个级别缩进4 个空格,可以这样写代码:

    var book = {
        "title": "Professional JavaScript",
        "authors": ["Nicholas C. Zakas"],
        edition: 3,
        year: 2011
    };
    var jsonText = JSON.stringify(book, null, 4);

    运行一下

    保存在jsonText中的字符串如下所示:

    {
        "title": "Professional JavaScript",
        "authors": ["Nicholas C. Zakas"],
        "edition": 3,
        "year": 2011
    }

    不知道读者注意到没有,JSON.stringify()也在结果字符串中插入了换行符以提高可读性。只要传入有效的控制缩进的参数值,结果字符串就会包含换行符。(只缩进而不换行意义不大。)最大缩进空格数为10,所有大于10 的值都会自动转换为10。
    如果缩进参数是一个字符串而非数值,则这个字符串将在JSON 字符串中被用作缩进字符(不再使用空格)。在使用字符串的情况下,可以将缩进字符设置为制表符,或者两个短划线之类的任意字符。

    var jsonText = JSON.stringify(book, null, " - -");

    这样,jsonText 中的字符串将变成如下所示:

    {
    --"title": "Professional JavaScript",
    --"authors": [
    ----"Nicholas C. Zakas"
    --],
    --"edition": 3,
    --"year": 2011
    }

    缩进字符串最长不能超过10 个字符长。如果字符串长度超过了10 个,结果中将只出现前10 个字符。

    3. toJSON()方法

    有时候,JSON.stringify()还是不能满足对某些对象进行自定义序列化的需求。在这些情况下,可以给对象定义toJSON()方法,返回其自身的JSON 数据格式。原生Date 对象有一个toJSON()方法,能够将JavaScript 的Date 对象自动转换成ISO 8601 日期字符串(与在Date 对象上调用toISOString()的结果完全一样)。
    可以为任何对象添加toJSON()方法,比如:

    var book = {
    	"title": "Professional JavaScript",
    	"authors": ["Nicholas C. Zakas"],
    	edition: 3,
    	year: 2011,
    	toJSON: function() {
    		return this.title;
    	}
    };
    var jsonText = JSON.stringify(book);

    运行一下
    以上代码在book 对象上定义了一个toJSON()方法,该方法返回图书的书名。与Date 对象类似,这个对象也将被序列化为一个简单的字符串而非对象。可以让toJSON()方法返回任何值,它都能正常工作。比如,可以让这个方法返回undefined,此时如果包含它的对象嵌入在另一个对象中,会导致它的值变成null,而如果它是顶级对象,结果就是undefined。
    toJSON()可以作为函数过滤器的补充,因此理解序列化的内部顺序十分重要。假设把一个对象传入JSON.stringify(),序列化该对象的顺序如下。
    (1) 如果存在toJSON()方法而且能通过它取得有效的值,则调用该方法。否则,返回对象本身。
    (2) 如果提供了第二个参数,应用这个函数过滤器。传入函数过滤器的值是第(1)步返回的值。
    (3) 对第(2)步返回的每个值进行相应的序列化。
    (4) 如果提供了第三个参数,执行相应的格式化。
    无论是考虑定义toJSON()方法,还是考虑使用函数过滤器,亦或需要同时使用两者,理解这个顺序都是至关重要的。

    20.2.3 解析选项

    JSON.parse()方法也可以接收另一个参数,该参数是一个函数,将在每个键值对儿上调用。为了区别JSON.stringify()接收的替换(过滤)函数(replacer),这个函数被称为还原函数(reviver),但实际上这两个函数的签名是相同的——它们都接收两个参数,一个键和一个值,而且都需要返回一个值。
    如果还原函数返回undefined,则表示要从结果中删除相应的键;如果返回其他值,则将该值插入到结果中。在将日期字符串转换为Date 对象时,经常要用到还原函数。例如:

    var book = {
    	"title": "Professional JavaScript",
    	"authors": ["Nicholas C. Zakas"],
    	edition: 3,
    	year: 2011,
    	releaseDate: new Date(2011, 11, 1)
    };
    var jsonText = JSON.stringify(book);
    var bookCopy = JSON.parse(jsonText,
    function(key, value) {
    	if (key == "releaseDate") {
    		return new Date(value);
    	} else {
    		return value;
    	}
    });
    alert(bookCopy.releaseDate.getFullYear());

    运行一下
    以上代码先是为book 对象新增了一个releaseDate 属性,该属性保存着一个Date 对象。这个对象在经过序列化之后变成了有效的JSON 字符串,然后经过解析又在bookCopy 中还原为一个Date对象。还原函数在遇到"releaseDate"键时,会基于相应的值创建一个新的Date 对象。结果就是bookCopy.releaseDate 属性中会保存一个Date 对象。正因为如此,才能基于这个对象调用getFullYear()方法。

    更多章节教程:http://www.shouce.ren/api/view/a/15218

  • 相关阅读:
    【leetcode】1215.Stepping Numbers
    【leetcode】1214.Two Sum BSTs
    【leetcode】1213.Intersection of Three Sorted Arrays
    【leetcode】1210. Minimum Moves to Reach Target with Rotations
    【leetcode】1209. Remove All Adjacent Duplicates in String II
    【leetcode】1208. Get Equal Substrings Within Budget
    【leetcode】1207. Unique Number of Occurrences
    【leetcode】689. Maximum Sum of 3 Non-Overlapping Subarrays
    【leetcode】LCP 3. Programmable Robot
    【leetcode】LCP 1. Guess Numbers
  • 原文地址:https://www.cnblogs.com/itzhoubao/p/6834412.html
Copyright © 2011-2022 走看看