目录
1.JSON
2.什么是 JSON?
3.JSON 发展史
4.为什么要使用 JSON?
5.JSON 的不足
6.JSON 应该如何存储?
7.什么时候会使用 JSON
7.1.定义接口
7.2.序列化
7.3.生成 Token
7.4.配置文件
8.JSON的语法规则
8.1.对象和数组
8.2.JSON的键值对
9.JSON数据类型
9.1.字符串
9.2.数字
9.3.真假
9.4.空
9.5.对象
9.6.数组
10.JSON对象(object)
11.JSON数组(array)
12.JSON如何注释
12.1.约定——使用特殊的键名
12.2.JSON5
13.jsoncpp库
13.1.json数据对象类——jsonvalue
13.2.json序列化类
13.3.json实现序列化案例
13.4.json反序列化类
13.5.json实现反序列化案例
1.JSON
JSON 全称“JavaScript Object Notation”,译为“JavaScript 对象简谱”或“JavaScript 对象表示法”,是一种轻量级的、基于文本的、开放的数据交换格式。JSON 在 Web 开发领域有着举足轻重的地位,如果您想在 Web 开发领域大展身手的话,就必须了解 JSON。
数据交换是指,两个设备之间建立连接并互相传递数据的过程。
尽管 JSON 的名称中包含“JavaScript”,但它并不是只能在 JavaScript 中使用,大多数编程语言都支持 JSON(有些本身就支持,有些可以通过第三方库得到支持),例如 JavaScript、Java、PHP、Python、C++ 等。
2.什么是 JSON?
JSON 是一种纯字符串形式的数据,它本身不提供任何方法(函数),非常适合在网络中进行传输。JavaScript、PHP、Java、Python、C++ 等编程语言中都内置了处理 JSON 数据的方法。
JSON 是基于 JavaScript(Standard ECMA-262 3rd Edition - December 1999)的一个子集,是一种开放的、轻量级的数据交换格式,采用独立于编程语言的文本格式来存储和表示数据,易于程序员阅读与编写,同时也易于计算机解析和生成,通常用于在 Web 客户端(浏览器)与 Web 服务器端之间传递数据。
在 JSON 中,使用以下两种方式来表示数据:
- Object(对象):键/值对(名称/值)的集合,使用花括号{ }定义。在每个键/值对中,以键开头,后跟一个冒号:,最后是值。多个键/值对之间使用逗号,分隔,例如{"name":"C语言中文网","url":"http://c.biancheng.net"};
- Array(数组):值的有序集合,使用方括号[ ]定义,数组中每个值之间使用逗号,进行分隔。
下面展示了一个简单的 JSON 数据:
{
"Name":"C语言中文网",
"Url":"http://c.biancheng.net/",
"Tutorial":"JSON",
"Article":[
"JSON 是什么?",
"JSONP 是什么?",
"JSON 语法规则"
]
}
这段JSON数据描述了一个名为“C语言中文网”的网站,包含了以下信息:
- Name(名称): "C语言中文网"
- Url(网址): C语言中文网:C语言程序设计门户网站(入门教程、编程软件)
- Tutorial(教程): "JSON"
- Article(文章列表):
- "JSON 是什么?"
- "JSONP 是什么?"
- "JSON 语法规则"
这表示在“C语言中文网”网站上,有关于JSON的教程,并且列出了三篇相关的文章标题。
3.JSON 发展史
2000 年初,Douglas Crockford(道格拉斯·克罗克福特)发明了 JSON,并从 2001 年开始推广使用。同年 4 月,位于旧金山湾区某车库的一台计算机发出了首个 JSON 格式的数据,这是计算机历史上的重要时刻。
2005-2006 年,JSON 正式成为主流的数据格式,雅虎、谷歌等知名网站开始广泛使用 JSON 格式。
2013 年,ECMA International(欧洲计算机制造商协会)制定了 JSON 的语法标准——ECMA-404。
经过 20 年左右的发展,JSON 已经替代了 XML,成为了 Web 开发中首选的数据交换格式。
4.为什么要使用 JSON?
JSON 并不是唯一能够实现在互联网中传输数据的方式,除此之外还有一种 XML 格式。JSON 和 XML 能够执行许多相同的任务,那么我们为什么要使用 JSON,而不是 XML 呢?
之所以使用 JSON,最主要的原因是 JavaScript。众所周知,JavaScript 是 Web 开发中不可或缺的技术之一,而 JSON 是基于 JavaScript 的一个子集,JavaScript 默认就支持 JSON,而且只要您学会了 JavaScript,就可以轻松地使用 JSON,不需要学习额外的知识。
另一个原因是 JSON 比 XML 的可读性更高,而且 JSON 更加简洁,更容易理解。
与 XML 相比,JSON 具有以下优点:
- 结构简单、紧凑:与 XML 相比,JSON 遵循简单、紧凑的风格,有利于程序员编辑和阅读,而 XML 相对比较复杂;
- 更快:JSON 的解析速度比 XML 更快(因为 XML 与 HTML 很像,在解析大型 XML 文件时需要消耗额外的内存),存储同样的数据,JSON 格式所占的存储空间更小;
- 可读性高:JSON 的结构有利于程序员阅读。
5.JSON 的不足
任何事物都不可能十全十美,JSON 也不例外,比如:
- 只有一种数字类型:JSON 中只支持 IEEE-754 双精度浮点格式,因此您无法使用 JSON 来存储许多编程语言中多样化的数字类型;
- 没有日期类型:在 JSON 中您只能通过日期的字符串(例如:1970-01-01)或者时间戳(例如:1632366361)来表示日期;
- 没有注释:在 JSON 中无法添加注释;
- 冗长:虽然 JSON 比 XML 更加简洁,但它并不是最简洁的数据交换格式,对于数据量庞大或用途特殊的服务,您需要使用更加高效的数据格式。
6.JSON 应该如何存储?
JSON 数据可以存储在 .json 格式的文件中(与 .txt 格式类似,都属于纯文本文件),也可以将 JSON 数据以字符串的形式存储在数据库、Cookie、Session 中。
要使用存储好的 JSON 数据也非常简单,不同的编程语言中提供了不同的方法来检索和解析 JSON 数据,例如 JavaScript 中的 JSON.parse() 和 JSON.stringify()、PHP 中的 json_decode() 和 json_encode()。
7.什么时候会使用 JSON
简单了解了 JSON 之后,我们再来看看什么时候适合使用 JSON。
7.1.定义接口
JSON 使用最多的地方莫过于 Web 开发领域了,现在的数据接口基本上都是返回 JSON 格式的数据,比如:
- 使用 Ajax 异步加载的数据;
- RPC 远程调用;
- 前后端分离,后端返回的数据;
- 开发 API,例如百度、高德的一些开放接口。
这些接口一般都会提供一个接口文档,说明接口调用的方法、需要的参数以及返回数据的介绍等。
7.2.序列化
程序在运行时所有的变量都是存储在内存中的,如果程序重启或者服务器宕机,这些数据就会丢失。一般情况下运行时变量并不是很重要,丢了就丢了,但有些数据则需要保存下来,供下次程序启动或其它程序使用。
我们可以将这些数据保存到数据库中,也可以保存到一个文件中,这个将内存中数据保存起来的过程称为序列化。序列化在 Python 中称为 pickling,在其他语言中也被称为 serialization、marshalling、flattening 等等,都是一个意思。
通常情况下,序列化是将程序中的对象直接转换为可保存或者可传输的数据,但这样会保存对象的类型信息,无法做到跨语言使用,例如我们使用 Python 将数据序列化到硬盘,然后使用 Java 来读取这份数据,这时由于不同编程语言的数据类型不同,就会造成读取失败。如果在序列化之前,先将对象信息转换为 JSON 格式,则不会出现此类问题。
7.3.生成 Token
Token 的形式多种多样,JSON、字符串、数字等都可以用来生成 Token,JSON 格式的 Token 最有代表性的是 JWT(JSON Web Tokens)。
随着技术的发展,分布式 Web 应用越来越普及,通过 Session 管理用户登录状态的成本越来越高,因此慢慢发展为使用 Token 做登录身份校验,然后通过 Token 去取 Redis 中缓存的用户信息。随着之后 JWT 的出现,校验方式变得更加简单便捷,无需再通过 Redis 缓存,而是直接根据 Token 读取保存的用户信息。
7.4.配置文件
我们还可以使用 JSON 来作为程序的配置文件,最具代表型的是 npm(Node.js 的包管理工具)的 package.json 包管理配置文件,如下所示:
{
"name": "server",
"version": "0.0.0",
"private": true,
"main": "server.js",
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"cookie-parser": "~1.4.3",
"debug": "~2.6.9",
"express": "~4.16.0",
"http-errors": "~1.6.2",
"jade": "~1.11.0",
"morgan": "~1.9.0"
}
}
虽然 JSON 可以用来定义配置文件,但由于 JSON 中不能添加注释,使得配置文件的可读性较差。
8.JSON的语法规则
8.1.对象和数组
JSON 的语法与 JavaScript 中的对象很像,在 JSON 中主要使用以下两种方式来表示数据:
- Object(对象):键/值对(名称/值)的集合,使用花括号{ }定义。在每个键/值对中,以键开头,后跟一个冒号:,最后是值。多个键/值对之间使用逗号,分隔,例如{"name":"C语言中文网","url":"http://c.biancheng.net"};
- Array(数组):值的有序集合,使用方括号[ ]定义,数组中每个值之间使用逗号,进行分隔。
下面展示了一个简单的 JSON 数据:
{
"website": {
"name": "C语言中文网",
"url": "http://c.biancheng.net/",
"articles": [
{
"title": "JSON 是什么?",
"content": "JSON (JavaScript Object Notation) 是一种轻量级的数据交换格式。"
},
{
"title": "JSON 语法规则",
"content": "JSON 对象在键和字符串值周围使用双引号,并使用逗号分隔多个项。"
},
{
"title": "JSON 与 JavaScript 的关系",
"content": "JSON 是 JavaScript 对象的文字表示法,但它独立于语言,许多编程语言都支持 JSON 格式。"
}
]
}
}
注意:所有 JSON 数据需要包裹在一个花括号中,类似于 JavaScript 中的对象。
在这个JSON数据中:
- 最外层是一个对象(由{}包围),它有一个键"website",其值也是一个对象。
- 内层的"website"对象有三个键:"name"、"url"和"articles"。
- "name"和"url"的值是字符串。
- "articles"的值是一个数组(由[]包围),数组中的每个元素都是一个对象,代表一篇文章。
- 每篇文章对象有两个键:"title"和"content",它们的值分别是文章的标题和内容。
这种结构使得JSON成为一种非常灵活和强大的数据表示方式,能够轻松地表示复杂的数据关系。在Web开发中,JSON经常被用作前后端之间交换数据的格式。
8.2.JSON的键值对
JSON 数据是以键/值对(名称/值)的形式书写的,键表示数据的名称,需要以字符串的形式定义(在双引号中定义),后面紧跟一个冒号,最后是值。
如下例所示:
{
"name": "C语言中文网", // 键 "name" 对应的值是字符串 "C语言中文网"
"age": 15, // 键 "age" 对应的值是数字 15
"isActive": true, // 键 "isActive" 对应的值是布尔值 true
"foundedYear": 2005, // 键 "foundedYear" 对应的值是数字(整数)2005
"website": "http://c.biancheng.net", // 键 "website" 对应的值是字符串(一个网址)
"visitors": { //键 "visitors" 是一个字符串,其值也是一个JSON对象(由花括号 {} 包围)。
"today": 12345, // 嵌套对象中的键 "today" 对应的值是数字 12345
"yesterday": 11223 // 嵌套对象中的键 "yesterday" 对应的值是数字 11223
},
"articles": [ // 键 "articles" 对应的值是一个数组
{ //数组里面有两个json对象
"title": "JSON入门",
"author": "张三"
},
{
"title": "JSON与JavaScript",
"author": "李四"
}
]
}
在这个JSON数据中:
- 最外层的花括号 {} 表示这是一个JSON对象。
- 对象内部包含了多个键/值对,每个键后面都跟着一个冒号 :,然后是对应的值。
- 值可以是字符串(如 "C语言中文网")、数字(如 15)、布尔值(如 true)、另一个JSON对象(如 "visitors" 键对应的值)或JSON数组(如 "articles" 键对应的值)。
- 数组使用方括号 [] 表示,数组中的每个元素可以是任何JSON支持的数据类型,包括字符串、数字、布尔值、对象或数组(形成嵌套数组)。
请注意,JSON中的键名(如 "name"、"age" 等)必须是唯一的,且必须以字符串的形式出现(即使用双引号包围)。而值则可以是上述提到的任何数据类型。
JSON中的键
- 定义:JSON数据由一系列键/值对(也称为名称/值对)构成。键用于标识数据的名称。
- 格式:键必须以字符串的形式给出,且被双引号(")包围。
- 唯一性:在同一个JSON对象中,键必须是唯一的,不能重复。如果出现重复的键,后定义的键/值对会覆盖先前的。
JSON中的值
数据类型:JSON中的值可以是以下几种类型之一:
- 数字(整数或浮点数)
- 字符串(被双引号包围)
- 布尔值(true或false)
- 数组(由方括号[]包围的一系列值)
- 对象(由花括号{}包围的一系列键/值对)
- null(表示空值)
示例
以下是一个包含多种数据类型的JSON对象示例:
{
"websiteName": "C语言中文网", // 字符串值
"establishedYear": 2000, // 数字值(整数)
"isActive": true, // 布尔值
"articles": [ // 数组值
"JSON基础教程",
"JavaScript与JSON的关系",
{
"title": "JSON高级应用",
"views": 12345
}
],
"contact": { // 对象值
"email": "info@c.biancheng.net",
"phone": "123-456-7890"
},
"logo": null // null值
}
注意事项
- 双引号:JSON中的键和字符串值必须使用双引号包围,不能使用单引号。
- 逗号分隔:不同的键/值对之间以及数组中的元素之间都需要使用逗号进行分隔。
- 数字格式:JSON中的数字不能是八进制或十六进制格式,必须是十进制。
- 键的唯一性:确保在同一个JSON对象中,每个键都是唯一的,以避免数据覆盖的问题。
在使用 JSON 时,有以下几点需要注意:
- JSON 是一段包裹在花括号中的数据,数据由若干键/值对组成;
- 键和值之间使用冒号分隔;
- 不同的键/值对之间使用逗号分隔;
- 键需要以字符串的形式定义(即使用双引号包裹,注意:不能使用单引号);
- 值可以是数字、字符串、布尔值、数组、对象、null;
- 键必须是唯一的,不能重复,否则后定义的键/值对会覆盖前面定义的键/值对;
- JSON 中不可以使用八进制或十六进制表示数字。
9.JSON数据类型
JSON 是 Web 开发中使用最广泛的数据交换格式,它独立于编程语言,能够被大多数编程语言使用。我们来详细介绍一下 JSON 中支持的数据类型。
JSON 中支持的数据类型可以分为简单数据类型和复杂数据类型两种。
其中简单数据类型包括:
- string(字符串)
- number(数字)
- boolean(布尔值)
- null(空)
复杂数据类型包括:
- Array(数组)
- Object(对象)
9.1.字符串
在JSON(JavaScript Object Notation)中,字符串必须使用双引号(")来界定,而不能使用单引号(')。这个规则是JSON格式的一个核心部分,它确保了数据的一致性和可解析性。
此外,JSON字符串可以包含零个或多个Unicode字符。Unicode是一个广泛使用的字符编码标准,它支持世界上几乎所有的书写系统。这意味着JSON字符串可以包含英文字母、数字、标点符号,以及各种特殊字符和符号,包括中文字符、日文字符、韩文字符、阿拉伯数字等。
另外,JSON 的字符串中也可以包含一些转义字符,例如:
\\
:表示反斜线字符本身。\/
:表示正斜线(在大多数情况下,正斜线不需要转义,但在某些上下文中为了避免歧义可能会使用)。\"
:表示双引号字符。\b
:表示退格符(BS,U+0008)。\f
:表示换页符(FF,U+000C)。\n
:表示换行符(LF,U+000A)。\r
:表示回车符(CR,U+000D)。\t
:表示水平制表符(TAB,U+0009)。\uXXXX
:表示一个由四位十六进制数字指定的Unicode字符。例如,\u0041
表示大写字母A。
示例代码如下:
{
"name":"C语言中文网",
"url":"http://c.biancheng.net/",
"title":"JSON 数据类型"
}
9.2.数字
在JSON中,数字不区分整型和浮点型,而是统一使用IEEE-754双精度浮点格式来表示。这意味着JSON中的数字可以是小数点表示的浮点数,也可以是整数(在这种情况下,它们会被隐式地当作浮点数处理,但不会有任何精度损失,因为整数只是浮点数的一个子集)。
以下是一些JSON中数字的示例代码:
{
"age": 30, // 整数,隐式地作为浮点数处理
"height": 1.75, // 浮点数
"salary": 50000.00, // 浮点数,带有两位小数
"scientificNotation": 1.23e4, // 科学记数法表示的数字,等于12300
"negativeNumber": -42, // 负整数
"fraction": 0.75, // 分数形式的小数
"zero": 0 // 零
}
在这些示例中:
- "age" 是一个整数,但在JSON中它会被当作浮点数来处理。
- "height" 是一个普通的浮点数。
- "salary" 是一个带有两位小数的浮点数,虽然在实际存储和传输时可能会根据精度需求进行舍入。
- "scientificNotation" 使用了科学记数法来表示一个较大的数字。
- "negativeNumber" 是一个负整数。
- "fraction" 是一个分数形式的小数。
- "zero" 是数字零。
此外,JSON的数据格式还有以下限制
- 不使用八进制和十六进制:
- 在JSON中,你不能使用八进制(以0开头)或十六进制(以0x或0X开头)来表示数字。所有的数字都必须以十进制形式表示,或者通过科学记数法来表示。
- 使用e或E表示10的指数:
- JSON支持科学记数法,允许你使用e或E来表示10的指数。例如,1.23e4表示1.23 * 10^4,即12300。同样地,5.67E-3表示5.67 * 10^-3,即0.00567。
这些规则确保了JSON中的数字格式是清晰且一致的,同时也使得JSON数据能够轻松地在不同的编程语言和系统之间交换和解析。
像下面这种写法都是错误的
{
// 注意:以下是不合法的JSON数字表示(八进制和十六进制)
"octalNumber": 052, // 不合法,JSON中不支持八进制
"hexadecimalNumber": 0x2A // 不合法,JSON中不支持十六进制
}
9.3.真假
JSON 中的布尔值与 JavaScript、PHP、Java 等编程语言中相似,有两个值,分别为 true(真)和 false(假),如下例所示:
{
"message" : true,
"pay_succeed" : false
}
9.4.空
null(空)是 JSON 中的一个特殊值,表示没有任何值,当 JSON 中的某些键没有具体值时,就可以将其设置为 null,如下例所示:
{
"id" : 1,
"visibility" : true,
"popularity" : null
}
9.5.对象
JSON 中,对象由花括号{ }以及其中的若干键/值对组成,一个对象中可以包含零个或多个键/值对,每个键/值对之间需要使用逗号,分隔,如下例所示:
{
"author": {
"name": "C语言中文网",
"url": "http://c.biancheng.net/"
}
}
9.6.数组
JSON 中,数组由方括号[ ]和其中的若干值组成,值可以是 JSON 中支持的任意类型,每个值之间使用逗号,进行分隔,如下例所示:
{
"course": [
"JSON 教程", // 字符串元素,表示一个课程名称
"JavaScript 教程", // 字符串元素,表示另一个课程名称
"HTML 教程", // 字符串元素,表示又一个课程名称
{
"website": "C语言中文网", // 对象元素,包含一个网站名称的键值对
"url": "http://c.biancheng.net" // 对象元素中的另一个键值对,表示网站的URL
},
[
3.14, // 数组元素,包含一个浮点数
true // 数组元素,包含一个布尔值
],
null // 数组元素,表示空值
]
}
在这个JSON对象中:
- "course" 键的值是一个数组,这个数组可以包含任意数量的元素,且元素类型可以是多样的。
- 数组的前三个元素是字符串,分别表示三个不同的课程名称。
- 第四个元素是一个对象,这个对象包含两个键值对,分别表示一个网站的名称和URL。
- 第五个元素是一个数组,这个数组包含了一个浮点数(3.14)和一个布尔值(true)。这种数组内嵌套其他数组或对象的结构在JSON中是合法的,并且非常灵活。
- 第六个元素是 null,表示一个空值或不存在的值。在JSON中,null 是一个合法的值,用于表示空或未定义的状态。
JSON的这种结构允许您以非常灵活的方式表示复杂的数据。在这个例子中,"course" 键的值不仅包含了简单的字符串,还包含了嵌套的对象和数组,以及一个空值。
10.JSON对象(object)
在 JSON 中,对象是一个无序的、键/值对的集合,一个对象以左花括号{开始,以右花括号}结束,左右花括号之间为对象中的若干键/值对。键/值对中,键必须是字符串类型(即使用双引号将键包裹起来),而值可以是 JSON 中的任意类型,键和值之间需要使用冒号:分隔开,不同的键/值对之间需要使用逗号,分隔开。关于 JSON 中支持的数据类型,您可以查阅《JSON数据类型》一节。
下面来看一个 JSON 对象的例子:
{
"website": {
"name" : "C语言中文网",
"url" : "http://c.biancheng.net/"
}
}
通过上面的示例可以看出,整个 JSON 就是一个对象类型,在这个对象中包含一个名为“website”的键,与键所对应的值同样也是一个对象,对象中包含“name”、“url”等键,以及与键所对应的值。
在 JSON 中使用对象类型时,有以下几点需要注意:
- 对象必须包裹在花括号{ }中;
- 对象中的内容需要以键/值对的形式编写;
- 键必须是字符串类型,即使用双引号" "将键包裹起来;
- 值可以是任意 JSON 中支持的数据类型(例如字符串、数字、对象、数组、布尔值、null 等);
- 键和值之间使用冒号进行分隔;
- 不同键/值对之间使用逗号进行分隔;
- 对象中的最后一个键/值对末尾不需要添加逗号。
另外,JSON 对象中可以嵌套其它的任意类型,例如对象、数组等,如下例所示:
// 键/值对中,键必须使用双引号定义,值若是字符串类型也必须使用双引号定义
{
"name": "C语言中文网",
"age": 18,
"url": "http://c.biancheng.net/",
"course": {
"title": "JSON教程",
"list": [
"JSON是什么?",
"JSON语法规则",
"JSON数据类型" // 这个地方不能添加逗号,因为它是数组中最后一个值
] // 这个地方也不能添加逗号,因为它是对象中最后一个键/值对
} // 这个地方也不可以有逗号,因为它也是对象的最后一个键/值对
}
提示:上面示例中,注释不是 JSON 的一部分,因为 JSON 中不支持定义注释。使用上述 JSON 数据之前,需要先将注释内容删除。
11.JSON数组(array)
数组是值的有序集合,JSON 中的数组与 JavaScript 中的数组相似,同样需要使用方括号[ ]定义,方括号中为数组中的若干值,值可以是 JSON 中支持的任意类型(例如字符串、数字、布尔值、对象、数组等),每个值之间使用逗号,分隔开,具体语法格式如下:
[value_1, value_2, value_3, …, value_N]
下面来看一个 JSON 数组的示例:
{
"array":[
{
"name":"C语言中文网",
"url":"http://c.biancheng.net/",
"course":"JSON教程"
},
[
"JSON是什么?",
"JSON语法规则",
"JSON数据类型"
],
"JSON",
18,
true
]
}
通过上面的示例可以看出,JSON 中数组与对象不同,对象是由若干键/值对组成,而数组则是由若干值构成的。数组中的值可以是 JSON 中的任意类型,在上面的示例中就分别使用了对象、数组、字符串、数字、布尔值等类型。
在 JSON 中使用数组时,有以下几点需要注意:
- 数组必须使用方括号[ ]定义;
- 数组的内容由若干值组成;
- 每个值之间需要使用逗号,进行分隔;
- 最后一个值末尾不需要添加逗号;
- 数组中的值可以是 JSON 中的任何类型,例如字符串、数字、对象、数组、布尔值、null 等。
12.JSON如何注释
JSON 是一种纯粹的数据交换格式,其简单、灵活的特性使得 JSON 适合被用于各种不同的场景,例如在配置文件中、在接口返回的数据中都会用到 JSON 格式。然而 JSON 却有一个非常明显的缺点,那就是 JSON 不能像编程语言那样添加注释,JSON 中的任何内容都会看作数据的一部分。
之所以不允许添加注释,是因为 JSON 主要是用来存储数据的,过多的注释会对数据的可读性造成影响,同时也会造成数据体积变大,进而影响数据传输、解析的速度。
但是,在某些场景中,我们又需要使用注释。例如在 JSON 格式的配置文件中,我们希望以注释的形式对配置信息进行解释说明;在接口返回的 JSON 数据中,我们希望通过注释描述数据的含义等等。那么我们要怎么在 JSON 数据中添加注释呢?下面就来介绍几种方法。
12.1.约定——使用特殊的键名
在 JSON 中添加注释并不是标准做法,因为 JSON 本身不支持注释。然而,通过一些约定俗成的技巧,我们可以在 JSON 数据结构中模拟注释的效果。
想要在 JSON 中添加注释,我们可以在要添加注释键/值对的前面(或后面)添加一个同名的键,并在键名中添加一个特殊字符加以区分,例如@、#、?、_、/等,然后在与键对应的值中定义注释的内容。
注意:在键名中添加特殊字符时,应尽量避免:、{、}、[、]等 JSON 中常用的字符。
以下是一个示例,展示了如何使用特殊字符在键名中添加注释信息。
假设我们有一个简单的 JSON 对象,表示一个人的信息,并希望在其中添加一些注释来说明字段的用途:
{
"name": "John Doe",
"_name_comment": "The full name of the person",
"age": 30,
"_age_comment": "The age of the person in years",
"email": "john.doe@example.com",
"_email_comment": "The email address of the person"
}
在这个示例中,我们使用了以下约定:
- 实际的字段名称(如 name、age、email)保持原样。
- 注释字段通过在原始字段名称前添加下划线 _ 和后缀 _comment 来表示(如 _name_comment、_age_comment、_email_comment)。
这样,任何解析 JSON 的程序都可以忽略这些注释字段,而人类读者可以很容易地理解每个字段的用途。
当然,你也可以选择其他特殊字符或约定来区分实际字段和注释字段,只要确保这些字符或约定在 JSON 数据中不会引起歧义或解析错误。例如,使用 @ 或 # 作为前缀:
{
"name": "John Doe",
"@name_comment": "The full name of the person",
"age": 30,
"@age_comment": "The age of the person in years",
"email": "john.doe@example.com",
"@email_comment": "The email address of the person"
}
或者,如果希望注释字段更加明显,可以使用更长的前缀,如 comment_:
{
"name": "John Doe",
"comment_name": "The full name of the person",
"age": 30,
"comment_age": "The age of the person in years",
"email": "john.doe@example.com",
"comment_email": "The email address of the person"
}
无论选择哪种方法,关键是确保这种约定在团队或项目中是一致的,并且易于理解和维护。
12.2.JSON5
JSON5是对JSON(JavaScript Object Notation)的一种扩展,旨在提供更灵活、更人性化的数据交换格式,同时仍然保持与JSON的兼容性。以下是对JSON5的详细介绍:
一、JSON5的特性
- 注释:
- JSON5允许在数据中添加注释,这对于提高代码的可读性和可维护性非常有帮助。无论是单行注释还是多行注释,JSON5都能很好地支持。
- 尾随逗号:
- 在JSON中,对象或数组的最后一个键值对或元素后面不能有逗号,但在JSON5中这是允许的。这一特性使得在编写和修改JSON数据时更加方便,减少了因缺少或多余的逗号而导致的错误。
- 数据类型和语法特性:
- JSON5提供了比JSON更多的数据类型和语法特性。例如,它允许使用单引号包裹字符串、支持十六进制数字表示、允许使用小数点开头或结尾的数字、支持正无穷大、负无穷大和NaN等。
- 日期和二进制数据:
- JSON5可以直接表示日期对象,而不需要将其转换为字符串。此外,它还提供了表示二进制数据的机制。
- 正则表达式和函数:
- 虽然这在数据交换中并不常见,但JSON5允许直接表示正则表达式对象和函数。这一特性在某些特定场景下可能会非常有用。
二、JSON5与JSON的兼容性
- 由于JSON5是JSON的超集,所以所有有效的JSON数据也都是有效的JSON5数据。这意味着在使用JSON5时,可以很容易地将其转换回标准的JSON格式,以便与其他系统或工具交换数据。
三、JSON5的用途
- JSON5的主要用途是在需要更多灵活性和可读性的场景中,比如配置文件、应用程序设置、测试数据等。在这些场景下,JSON5的注释、尾随逗号等特性可以大大提高代码的可读性和可维护性。
四、如何使用JSON5
- 由于JSON5不是标准的JSON,因此不能使用标准的JSON解析器(如JavaScript中的
JSON.parse()
)来解析它。需要使用支持JSON5的解析器,如json5
库。在使用之前,需要先安装这个库,然后才能使用它来解析或生成JSON5数据。
五、注意事项
- 并非所有的JSON解析器都能处理JSON5格式的数据。因此,在使用JSON5时,需要确保所使用的工具或库支持这种格式。
- 在某些场景下,如果数据需要在多种环境中交换,那么使用标准的JSON可能更为安全。因为虽然JSON5提供了更多的灵活性和可读性,但并不是所有的系统或工具都支持它。
13.jsoncpp库
jsoncpp 是一个广泛使用的 C++ 库,用于处理 JSON(JavaScript Object Notation)数据格式。它提供了将 C++ 数据对象序列化为 JSON 格式字符串的功能,以及将 JSON 格式字符串反序列化为 C++ 数据对象的功能。
以下是对 jsoncpp 库的一些关键特性和使用方法的概述:
关键特性
- 序列化与反序列化:jsoncpp 可以将 C++ 对象(如 STL 容器、基本数据类型等)转换为 JSON 格式的字符串。同样,它也可以将 JSON 格式的字符串解析回 C++ 对象。
- 易于使用:提供了直观的 API,使得序列化和反序列化过程相对简单。
- 支持多种数据类型,包括整数、浮点数、字符串、布尔值、数组(对应 C++ 中的 std::vector 等)和对象(对应 C++ 中的 std::map、std::unordered_map 或自定义类)。
- 跨平台:jsoncpp 可以在多种操作系统上运行,包括 Windows、Linux 和 macOS。
- 性能:对于大多数应用场景,jsoncpp 提供了足够的性能。然而,在极端情况下,可能需要考虑其他更高效的 JSON 库。
13.1.json数据对象类——jsonvalue
我们可以在我们安装的jsoncpp库里面找到下面这个文件
ls /usr/include/jsoncpp/json/
我们现在要讲的value就在value.h里面
我们打开来
vim /usr/include/jsoncpp/json/value.h
往下翻就能看到
非常多信息啊,我们只讲一小部分啊!!
//Json数据对象类
class Json::Value{
Value &operator=(const Value &other); //Value重载了[]和=,因此所有的赋值和获取数据都可以通过
Value& operator[](const std::string& key);//简单的方式完成 val["姓名"] = "小明";
Value& operator[](const char* key);
Value removeMember(const char* key);//移除元素
const Value& operator[](ArrayIndex index) const; //val["成绩"][0]
Value& append(const Value& value);//添加数组元素val["成绩"].append(88);
ArrayIndex size() const;//获取数组元素个数 val["成绩"].size();
std::string asString() const;//转string string name = val["name"].asString();
const char* asCString() const;//转char* char *name = val["name"].asCString();
Int asInt() const;//转int int age = val["age"].asInt();
float asFloat() const;//转float
bool asBool() const;//转 bool
};
注意:代码片段是一个简化的、概念性的 Json::Value 类的声明,它模拟了 jsoncpp 库中 Json::Value 类的一些关键功能。然而,请注意,这并不是 jsoncpp 库中实际的 Json::Value 类的完整或准确实现。以下是对您提供的类声明的一些解释和补充:
- 赋值运算符重载:
Value &operator=(const Value &other);
这个成员函数允许您将一个 Json::Value 对象赋值给另一个。在 jsoncpp 中,这是必需的,因为 Json::Value 是一个可以表示多种 JSON 数据类型(如对象、数组、字符串、数字等)的类。
- 下标运算符重载:
Value& operator[](const std::string& key);
Value& operator[](const char* key);
const Value& operator[](ArrayIndex index) const;
这些成员函数允许您使用字符串键或数组索引来访问 Json::Value 对象中的元素。这是处理 JSON 对象和数组时非常有用的功能。
- 移除成员:
Value removeMember(const char* key);
这个函数允许您从 JSON 对象中移除一个成员。注意,根据您的声明,这个函数的返回类型是 Value,但在 jsoncpp 的实际实现中,它可能返回被移除的值(如果有的话)或某种表示操作成功的状态。
- 添加数组元素:
Value& append(const Value& value);
这个函数允许您向 JSON 数组中添加一个新元素。在 jsoncpp 中,这是通过修改内部的数组结构来实现的。
- 获取数组大小:
ArrayIndex size() const;
这个函数返回 JSON 数组中元素的数量。在 jsoncpp 中,ArrayIndex 通常是一个无符号整数类型,用于表示数组索引。
- 类型转换函数:
std::string asString() const;
const char* asCString() const;
Int asInt() const; // 注意:在 jsoncpp 中,Int 是一个定义好的类型,通常是 long long 或类似的整数类型
float asFloat() const;
bool asBool() const;
这些函数允许您将 Json::Value 对象转换为相应的 C++ 数据类型。这是从 JSON 数据中提取信息时非常常见的操作。
重要提示:
- 在实际使用 jsoncpp 时,您应该包含 <json/json.h> 头文件,并链接到 jsoncpp 库。
13.2.json序列化类
我们可以在我们安装的jsoncpp库里面找到下面这个文件
ls /usr/include/jsoncpp/json/
我们现在要讲的writer就在writer.h里面
我们打开来
vim /usr/include/jsoncpp/json/writer.h
往下翻就能看到
json序列化类,低版本用这个更简单
// 抽象基类 Writer
class JSON_API Writer {
public:
virtual ~Writer() = default; // 虚析构函数
virtual std::string write(const Value& root) const = 0; // 纯虚函数,要求派生类实现
};
// FastWriter 类,继承自 Writer
class JSON_API FastWriter : public Writer {
public:
std::string write(const Value& root) const override; // 重写基类的纯虚函数
// ... 可能还需要其他成员函数或私有实现细节
};
// StyledWriter 类,继承自 Writer
class JSON_API StyledWriter : public Writer {
public:
std::string write(const Value& root) const override; // 重写基类的纯虚函数
// ... 可能提供设置缩进、换行等样式的成员函数
};
- Writer抽象基类
Writer类是一个抽象基类,它定义了一个纯虚函数write。这个函数的目的是将Value对象(代表JSON数据的值)序列化为字符串。由于Writer是一个抽象类,它不能直接实例化;相反,它提供了序列化功能的接口,供派生类实现。
- FastWriter和StyledWriter派生类
FastWriter和StyledWriter是Writer类的两个派生类,它们分别实现了write函数的不同版本。FastWriter注重序列化速度,可能省略了不必要的空格和换行符,以生成更紧凑的JSON字符串。而StyledWriter则可能添加了缩进和换行符,以提高JSON字符串的可读性。
注意:这个只适用于低版本JSON(我们这里就不用了)
同样的,也在writer.h里面
json序列化类,高版本推荐,如果用低版本的接口可能会有警告
// StreamWriter 抽象基类
class JSON_API StreamWriter {
public:
virtual ~StreamWriter() = default; // 虚析构函数
virtual int write(const Value& root, std::ostream* sout) const = 0; // 纯虚函数,要求派生类实现
};
// StreamWriterBuilder 类,继承自 StreamWriterFactory
class JSON_API StreamWriterBuilder : public StreamWriterFactory {
public:
std::unique_ptr<StreamWriter> create() const override; // 重写基类的纯虚函数
// ... 可能提供设置序列化选项的成员函数
};
- StreamWriter和StreamWriterBuilder类
StreamWriter是另一个抽象基类,它定义了一个将Value对象序列化到输出流中的write函数。与Writer不同,StreamWriter允许将序列化后的数据直接写入到std::ostream对象中,如文件流、字符串流等。
StreamWriterBuilder是一个工厂类,它继承自一个假设的StreamWriterFactory接口(在上面的代码中已经进行了定义)。StreamWriterBuilder实现了create函数,用于创建StreamWriter对象。通过提供不同的配置选项,StreamWriterBuilder可以生成具有不同序列化行为的StreamWriter实例。
这些适用于高版本的JSON,我们就使用这个
13.3.json实现序列化案例
当需要将一个Json::Value对象序列化为JSON字符串时,使用jsoncpp库的程序通常会执行以下步骤(基于下面的代码示例和新版jsoncpp库的接口):
准备数据:
- 创建一个Json::Value对象,并根据需要向其添加键值对、数组、嵌套对象等。
配置序列化选项(可选):
- 创建一个Json::StreamWriterBuilder对象。这个对象允许您配置序列化过程的各种选项,如缩进、排序键等。如果不需要特殊配置,可以使用默认设置。
创建序列化器:
- 使用Json::StreamWriterBuilder对象的newStreamWriter方法创建一个Json::StreamWriter的智能指针。这个序列化器将负责将Json::Value对象转换为JSON格式的字符串。
准备输出流:
- 创建一个输出流对象,如std::stringstream,用于存储序列化后的JSON字符串。也可以使用其他输出流,如std::ofstream来写入文件。
执行序列化:
- 调用Json::StreamWriter智能指针的write方法,将Json::Value对象作为参数传入,并指定输出流。write方法会遍历Json::Value对象的结构,并将其转换为JSON格式的字符串,然后写入到指定的输出流中。
获取和使用JSON字符串:
- 如果使用了std::stringstream,可以通过调用其str方法来获取序列化后的JSON字符串。然后,可以将这个字符串用于各种目的,如打印到控制台、发送到网络、保存到文件等。
清理资源(自动处理):
- 由于使用了智能指针(如std::unique_ptr),序列化器对象会在离开作用域时自动释放,无需手动清理。
这些步骤概述了使用jsoncpp库将Json::Value对象序列化为JSON字符串的典型过程。
main.cc
/*
json 序列化示例
*/
#include <iostream> // 引入标准输入输出流库
#include <sstream> // 引入字符串流库
#include <memory> // 引入内存管理库(用于智能指针)
#include <jsoncpp/json/json.h> // 引入jsoncpp库,用于JSON处理
int main()
{
// 定义并初始化一个常量字符指针,指向字符串"小明"
const char* name = "小明";
// 定义一个整型变量并初始化为18,表示年龄
int age = 18;
// 定义一个浮点型数组,存储三门课程的成绩
float score[] = {77.5, 88, 93.6};
// 定义一个Json::Value对象,作为JSON数据的根节点
Json::Value root;
// 向root中添加一个键值对,键为"name",值为name所指向的字符串
root["name"] = name;
// 向root中添加一个键值对,键为"age",值为整型变量age
root["age"] = age;
// 向root中添加一个键为"成绩"的数组,并依次添加score数组中的元素
// 使用append函数向数组中插入数据
root["成绩"].append(score[0]);
root["成绩"].append(score[1]);
root["成绩"].append(score[2]);
// 下面的代码被注释掉了,因为它直接打印Json::Value对象会调用其默认的输出流操作符,
// 这可能不会产生人类可读的JSON格式(具体取决于Json::Value的实现)
// std::cout << root << std::endl;
// 创建一个Json::StreamWriterBuilder对象,用于配置StreamWriter的创建
Json::StreamWriterBuilder swb;
// 使用swb的newStreamWriter方法创建一个StreamWriter的智能指针
// std::unique_ptr是一个智能指针,它会在离开作用域时自动释放所管理的资源
std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
// 创建一个字符串流对象,用于存储序列化后的JSON字符串
std::stringstream ss;
// 调用StreamWriter的write方法,将Json::Value对象序列化为JSON字符串,并写入到字符串流中
sw->write(root, &ss);
// 从字符串流中提取出序列化后的JSON字符串,并输出到标准输出流(控制台)
std::cout << ss.str() << std::endl;
return 0;
}
makefile
test:main.cc
g++ -o $@ $^ -ljsoncpp
.PHONY:clean
rm -rf test
13.4.json反序列化类
我们可以在我们安装的jsoncpp库里面找到下面这个文件
ls /usr/include/jsoncpp/json/
我们现在要讲的就在reader.h里面
我们打开来
vim /usr/include/jsoncpp/json/reader.h
往下翻就能看到类似于下面的内容
json反序列化类,低版本用起来更简单
class JSON_API Reader {
bool parse(const std::string& document, Value& root, bool collectComments = true);
}
代码片段展示了JSON反序列化框架的类设计。这里,Reader 类和 CharReader 类(及其工厂类 CharReaderBuilder)分别代表了不同版本的JSON解析接口。以下是对这些类的详细解释和工作机制的描述:
- Reader 类
Reader 类是一个用于JSON反序列化的类。它提供了一个 parse 成员函数,该函数接受一个JSON格式的字符串 document,一个 Value 类型的引用 root(用于存储解析后的JSON数据),以及一个可选的布尔值 collectComments(指示是否收集JSON文档中的注释)。
- document:包含JSON数据的字符串。
- root:一个 Value 类型的引用,用于存储解析后的JSON数据结构。Value 类通常是一个封装了JSON数据各种类型和结构的类。
- collectComments:一个可选的布尔值参数,默认为 true。如果设置为 true,则解析器会收集JSON文档中的注释,并将其存储在某种结构中(具体实现取决于 Value 类和 Reader 类的内部设计)。
parse 函数的返回值是一个布尔值,表示解析是否成功。如果解析失败,root 参数的状态将是未定义的,因此调用者需要检查返回值来确定是否成功解析了JSON数据。
注意:这个只适用于低版本JSON,我们这里就不使用了啊
json反序列化类,高版本更推荐
class JSON_API CharReader {
virtual bool parse(char const* beginDoc, char const* endDoc,
Value* root, std::string* errs) = 0;
}
class JSON_API CharReaderBuilder : public CharReader::Factory {
virtual CharReader* newCharReader() const;
}
- CharReader 类
CharReader 类是一个更底层的、面向字符数组的JSON解析接口。它定义了一个纯虚函数 parse,该函数接受字符数组的起始和结束指针,一个指向 Value 对象的指针(用于存储解析后的数据),以及一个指向字符串的指针(用于存储错误信息)。
- beginDoc 和 endDoc:分别指向JSON文档字符数组的起始和结束位置。
- root:一个指向 Value 对象的指针,用于存储解析后的JSON数据结构。
- errs:一个指向字符串的指针,用于存储解析过程中遇到的错误信息。如果解析成功,该字符串应该为空。
与 Reader 类不同,CharReader 类直接操作字符数组,而不是字符串。这使得它更加灵活,因为字符数组可以来自不同的源(如文件、网络流等),并且不需要额外的字符串复制操作。
- CharReaderBuilder 类
CharReaderBuilder 类是一个工厂类,它继承自一个假设的 CharReader::Factory 接口(该接口在您的代码中没有直接给出,但可以从上下文推断出来)。CharReaderBuilder 类实现了 newCharReader 函数,该函数用于创建 CharReader 对象的实例。
newCharReader 函数返回一个指向新创建的 CharReader 对象的指针。这个 CharReader 对象可以被用来解析JSON文档。
注意:这个只适用于高版本JSON,我们这里就使用它了啊
13.5.json实现反序列化案例
- 工作机制
- 当需要将一个JSON格式的字符串解析为程序对象时,可以使用 Reader 类。调用其 parse 函数,传入JSON字符串、一个 Value 对象的引用和一个可选的布尔值参数。
- 如果需要更底层的控制或性能优化,可以使用 CharReader 类。
- 首先,通过 CharReaderBuilder 类创建一个 CharReader 对象的实例。
- 然后,调用该实例的 parse 函数,传入字符数组的起始和结束指针、一个指向 Value 对象的指针和一个指向字符串的指针(用于存储错误信息)。
- 在解析过程中,Reader 或 CharReader 类会遍历JSON文档,并根据JSON的语法规则构建相应的 Value 对象结构。如果遇到错误(如语法错误、类型不匹配等),解析器会停止解析,并通过返回值或错误信息字符串来指示错误。
- 解析成功后,调用者可以访问 Value 对象来获取解析后的JSON数据。
main.cc
/*
json 反序列化示例
*/
#include <iostream> // 引入标准输入输出流库
#include <string> // 引入字符串库
#include <memory> // 引入内存管理库(用于智能指针)
#include <jsoncpp/json/json.h> // 引入jsoncpp库,用于JSON处理
int main()
{
// 定义一个字符串,存储JSON格式的文本数据
std::string str = R"({"姓名":"小黑", "年龄":19, "成绩":[58.5, 44, 20]})";
// 创建一个Json::Value对象,用于存储解析后的JSON数据
Json::Value root;
// 创建一个Json::CharReaderBuilder对象,用于配置CharReader的创建
Json::CharReaderBuilder crb;
// 使用crb的newCharReader方法创建一个CharReader的智能指针
std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
// 定义一个字符串,用于存储解析过程中可能发生的错误信息
std::string err;
// 调用CharReader的parse方法,尝试将字符串str解析为JSON数据,并存储在root中
// parse方法的参数包括:待解析的字符串的起始指针、结束指针、存储解析结果的Json::Value对象的指针、存储错误信息的字符串的指针
bool ret = cr->parse(str.c_str(), str.c_str() + str.size(), &root, &err);
// 检查解析是否成功
if(ret == false)
{
// 如果解析失败,输出错误信息并返回-1
std::cout << "parse error: " << err << std::endl;
return -1;
}
// 从解析后的JSON数据中提取"姓名"字段的值,并作为字符串输出
std::cout << root["姓名"].asString() << std::endl;
// 从解析后的JSON数据中提取"年龄"字段的值,并作为整数输出
std::cout << root["年龄"].asInt() << std::endl;
// 获取"成绩"字段(一个数组)的大小
int sz = root["成绩"].size();
// 遍历"成绩"数组,并输出每个元素的值
for(int i = 0; i < sz; i++)
{
std::cout << root["成绩"][i].asDouble() << std::endl; // 注意:这里使用asDouble()来确保输出浮点数的精度
}
// 程序正常结束,返回0
return 0;
}
JSON的反序列化(也称为解析)是将JSON格式的字符串数据转换回其对应的程序数据结构(如对象、数组、键值对等)的过程。在jsoncpp库中,这一过程主要通过Json::CharReader(或其派生类)的parse方法来实现。以下是对jsoncpp库中JSON反序列化实现过程的详细解释:
JSON字符串准备:
- 首先,你需要有一个包含JSON数据的字符串。这个字符串可能来自文件、网络请求、用户输入等。
Json::Value对象创建:
- 创建一个Json::Value对象,它将用于存储解析后的JSON数据。Json::Value是jsoncpp库中的一个核心类,用于表示JSON中的各种数据类型(如对象、数组、字符串、数字等)。
CharReader配置与创建:
- 使用Json::CharReaderBuilder来配置并创建一个Json::CharReader对象(或其派生类)。CharReaderBuilder允许你设置一些解析选项,但大多数情况下,你可以使用默认设置。
- Json::CharReader是负责实际解析JSON字符串的类。它通过其parse方法来完成解析工作。
调用parse方法:
- 调用Json::CharReader对象的parse方法,将JSON字符串、字符串的起始和结束指针、Json::Value对象的引用以及一个用于存储错误信息的字符串的引用作为参数传入。
- parse方法会尝试将JSON字符串解析为Json::Value对象表示的数据结构。如果解析成功,Json::Value对象将被填充为相应的数据;如果解析失败,错误信息将被存储在提供的字符串中。
检查解析结果:
- 解析完成后,你需要检查parse方法的返回值来确定解析是否成功。如果返回true,则表示解析成功;如果返回false,则表示解析失败,并且你可以通过提供的错误信息字符串来获取详细的错误信息。
访问解析后的数据:
- 一旦解析成功,你就可以使用Json::Value对象提供的各种方法来访问和操作解析后的数据了。例如,你可以使用operator[]来访问对象中的键或数组中的元素,并使用asString()、asInt()、asDouble()等方法来将数据转换为适当的类型。
在jsoncpp库的内部实现中,Json::CharReader的parse方法会逐字符地分析JSON字符串,并根据JSON的语法规则构建相应的Json::Value对象。这个过程涉及到对JSON各种数据类型的识别和处理(如对象、数组、字符串、数字、布尔值、null等),以及对JSON语法错误(如缺少引号、括号不匹配、逗号放错位置等)的检测和报告。
makefile
test:main.cc
g++ -o $@ $^ -ljsoncpp
.PHONY:clean
rm -rf test