← 全部文章

什么是 JSON?语法、数据类型、JavaScript 对象与 JSON Schema

学会 JSON 的语法和数据类型,看懂合法示例,弄清 JSON 与 JavaScript 对象的区别,并用 JSON Schema 校验真实的数据约定。

JSON,全称 JavaScript Object Notation,是一种用于在应用之间交换结构化数据的文本格式。它不是数据库,不是编程语言,也不是 schema。当一个系统要把数据交给另一个系统时,放到网络上传输、或者存进文件里的那段文本,就是 JSON。

如果你打开过 package.json、看过 REST 接口的响应、拷过 Webhook 载荷、改过 VS Code 的设置文件,或者瞄过浏览器的 Network 面板,那你早就见过 JSON 了。一个好用的心智模型很简单:JSON 是序列化之后的数据值。一个程序在发送前把对象变成 JSON 文本,另一个程序再把这段文本解析回自己语言里的数据结构。

{
  "id": "usr_42",
  "name": "Ada Lovelace",
  "active": true,
  "roles": ["admin", "editor"],
  "profile": {
    "timezone": "Europe/London",
    "newsletter": false
  }
}

这个例子是合法 JSON,因为它是一个完整的值、键和字符串都用双引号、truefalse 是小写,而且只包含 JSON 的数据类型。

一分钟看懂 JSON

问题 实用答案
JSON 是什么的缩写? JavaScript Object Notation
JSON 用来干什么? API 响应、请求体、配置文件、日志、数据导出,以及服务之间的消息
JSON 只能给 JavaScript 用吗? 不是。所有主流语言都能解析和生成它
JSON 和 JavaScript 对象是一回事吗? 不是。JSON 是语法更严格的文本,JavaScript 对象字面量是代码
JSON 有几种数据类型? 六种:对象、数组、字符串、数字、布尔、null
JSON 文件可以以数组开头吗? 可以。JSON 文本可以是任意单个 JSON 值,只是对象和数组最常见
JSON 能写注释吗? 不能。注释属于 JSONC,不属于严格 JSON
怎么校验 JSON? 用 JSON 解析器解析它,或者用严格的 JSON 校验器

什么是 JSON

JSON 是一套用来表示结构化数据的标准化文本语法。RFC 8259 把 JSON 描述为一种轻量、基于文本、与语言无关的数据交换格式。ECMA-404 定义了同一套核心语法,并有意把"这些数据是什么意思"留给使用它的系统去决定。

最后这点很关键。JSON 只告诉你数据该怎么写:

{
  "total": 1299,
  "currency": "USD"
}

它不告诉你 1299 到底是美元、美分、积分,还是库存件数。赋予数据含义的,是你的 API 约定、文档、JSON Schema、OpenAPI 规范或数据库模型。

一份 JSON 文档就是一个值

合法的 JSON 文档里,顶层有且只有一个 JSON 值。大多数真实文档用的是对象:

{
  "status": "ok",
  "items": []
}

数组也很常见:

[
  { "id": 1, "name": "Alice" },
  { "id": 2, "name": "Bob" }
]

基础类型同样是合法 JSON:

42
"hello"
null

实践中,API 响应通常还是套一层对象,因为这样才放得下元数据、分页信息、错误,以及将来要加的字段:

{
  "data": [
    { "id": 1, "name": "Alice" }
  ],
  "meta": {
    "page": 1,
    "hasMore": false
  }
}

JSON 的六种数据类型

JSON 有四种基础类型和两种结构类型,合起来常被称作 JSON 的六种数据类型。

类型 示例 用来表示 注意
对象 { "id": 1 } 具名字段和嵌套记录 键必须是字符串;重复键很危险
数组 [1, 2, 3] 有序列表 元素类型可以不一致,但混合数组更难用
字符串 "Ada" 文本、ID、日期、十进制金额 必须用双引号,转义要合法
数字 423.141.5e10 计数、度量、安全范围内的数值 不能有 NaNInfinity、十六进制、前导零
布尔 truefalse 二元状态 只能小写
Null null 明确表示"空值" 和"键不存在"不是一回事

对象

对象是用 {} 包起来的一组名值对。名字是字符串,所以必须加双引号。

{
  "name": "Alice",
  "age": 30,
  "active": true
}

RFC 8259 规定对象的名字应当唯一。很多解析器照样接受重复键,但它们的行为并不一致:有的保留最后一个值,有的保留第一个,有的把所有键值对都暴露出来。

别这么写:

{
  "role": "user",
  "role": "admin"
}

一个键要是重要,就只写一次。

数组

数组是用 [] 包起来的有序列表。

["apple", "banana", "cherry"]

数组里可以放对象:

[
  { "sku": "hat-blue", "quantity": 2 },
  { "sku": "mug-white", "quantity": 1 }
]

JSON 允许混合类型的数组:

["ok", 200, true, null]

语法上确实允许,但对有类型的代码、CSV 导出、分析程序和生成的 TypeScript 接口来说,这往往很别扭。在 API 设计里,元素类型一致的数组通常更好维护。

字符串

字符串是双引号里的 Unicode 文本。

"Hello, world!"

单引号不是 JSON:

'Hello, world!'

字符串内部的引号、反斜杠和控制字符必须转义:

{
  "quote": "She said \"hello\".",
  "path": "C:\\Users\\Ada\\notes.json",
  "lines": "first line\nsecond line"
}

如果某个东西长得像数字,但它其实是标识符(账号 ID、订单号、邮政编码、电话号码、外部系统的大 ID),就把它当字符串。这些值本来就不用来做算术,而字符串形式还能顺便避开前导零和精度问题。

数字

JSON 只有一种数字类型。

{
  "count": 42,
  "ratio": 0.875,
  "scientific": 1.5e10
}

数字是十进制的。JSON 不允许十六进制、八进制、NaNInfinity,也不允许前导零:

{
  "hex": 0xff,
  "nan": NaN,
  "badLeadingZero": 007
}

对 JavaScript 客户端来说,大于 9007199254740991(即 Number.MAX_SAFE_INTEGER)的整数要格外小心。如果某个 ID 可能超过这个范围,就用字符串传:

{
  "invoiceId": "9223372036854775807"
}

金额优先用整数最小单位,或者有明确文档说明的十进制字符串:

{
  "amountCents": 1299,
  "currency": "USD"
}

布尔

JSON 的布尔值只有 truefalse

{
  "emailVerified": true,
  "smsOptIn": false
}

TrueFalseyesno10 都不是 JSON 布尔值。它们在 Python、YAML、SQL、表单或表格软件里可能有意义,但严格 JSON 解析器要么拒绝它们,要么按别的类型处理。

Null

null 的意思是"这个字段在,但值是空的"。

{
  "middleName": null
}

它和"干脆不写这个键"不一样:

{}

在 API 约定里,你得想清楚自己要表达哪一种:

写法 含义
"middleName": null 字段存在,但没有值
没有 middleName 这个键 字段未知、未请求,或不适用
"middleName": "" 值是一个空字符串

这个区别在 PATCH 请求、表单提交、生成类型和数据库更新里都很要紧。

真正会咬人的 JSON 语法规则

严格 JSON 被刻意设计得很小。下面这些是开发者最常撞上的规则:

规则 非法 合法
键必须是双引号字符串 { name: "Alice" } { "name": "Alice" }
字符串用双引号 { "name": 'Alice' } { "name": "Alice" }
不能有尾随逗号 { "a": 1, } { "a": 1 }
不能有注释 { "a": 1 // note } { "a": 1 }
字面量只能小写 { "ok": True } { "ok": true }
不能有 undefined { "x": undefined } { "x": null }
顶层只能有一个值 {} {} [{ }, { }]

token 之间的空白是无意义的。下面两份文档解析出来是同一个值:

{"name":"Alice","active":true}
{
  "name": "Alice",
  "active": true
}

格式化让 JSON 更好读,但它不改变数据。

.json 文件是什么

.json 文件就是一个纯文本文件,内容是一份 JSON 文档。它不需要文件头、schema 声明、import 语句,也不需要结束标记。把合法的 JSON 文本存成 .json 扩展名,绝大多数工具就认了。

常见的例子:

文件 通常装的是什么
package.json Node.js 的包元数据、脚本和依赖
tsconfig.json TypeScript 编译器选项
manifest.json 浏览器扩展或 PWA 的元数据
appsettings.json .NET 应用配置
composer.json PHP 包元数据

打开 JSON 文件可以用文本编辑器、IDE、终端命令,或者 JSON 查看器。要新建一个,写一个合法的 JSON 值,存成 name.json 即可。

比如 users.json 里可能是:

[
  { "id": "usr_1", "name": "Alice", "role": "admin" },
  { "id": "usr_2", "name": "Bob", "role": "editor" }
]

文件很大或嵌套很深时,树形查看器通常比在原始文本里滚动省事得多。

JSON 与 JavaScript 对象字面量

JSON 长得像 JavaScript,但它们不是一回事。

这是 JavaScript 对象字面量:

const user = {
  id: 42,
  name: "Alice",
  active: true,
  lastSeen: undefined,
  greet() {
    return `Hello ${this.name}`;
  },
};

这是 JSON:

{
  "id": 42,
  "name": "Alice",
  "active": true
}

JSON 里不能有函数、方法、注释、尾随逗号、undefined、Symbol、Date 对象、MapSet 或计算属性名。它只承载数据。

在 JavaScript 里,用 JSON.parse()JSON.stringify() 在两者之间转换:

const text = '{"name":"Alice","active":true}';
const value = JSON.parse(text);

const output = JSON.stringify(value, null, 2);

如果你手上已经是对象,就别调用 JSON.parse(object);需要 JSON 文本时,调用 JSON.stringify(object)

在代码里解析和生成 JSON

所有主流语言都内置了 JSON 支持。

JavaScript:

const raw = '{"name":"Alice","age":30}';
const user = JSON.parse(raw);
const text = JSON.stringify(user);

Python:

import json

raw = '{"name":"Alice","age":30}'
user = json.loads(raw)
text = json.dumps(user)

Go:

package main

import "encoding/json"

type User struct {
  Name string `json:"name"`
  Age  int    `json:"age"`
}

func main() {
  var user User
  _ = json.Unmarshal([]byte(`{"name":"Alice","age":30}`), &user)
}

处理文件时,用支持文件的解析器,别自己读几块就急着解析。Python 里 json.load(file) 从文件对象读 JSON,json.loads(text) 解析字符串。

JSON 都用在哪儿

JSON 成为默认数据格式,是因为它对机器来说足够小、对人来说足够好读,而且几乎每种编程语言都支持。

常见用途:

  • API 的请求体和响应体,通常带 Content-Type: application/json
  • 支付服务商、认证服务商和各类 SaaS 发来的 Webhook。
  • 构建工具、编辑器、命令行工具和云服务的配置文件。
  • 结构化日志,常见形式是 JSON Lines 或 NDJSON。
  • 分析工具、数据库和内部管理系统的数据导出。
  • 服务、队列、Serverless 函数和 worker 之间的消息。
  • 应用仓库里的测试数据。

数据是层级结构、而且消费方清楚约定时,JSON 很合适。但它不适合富文本文档、注释很多的人工配置、二进制文件、表格式数据,以及那些需要严格数值精度却又没约定好表示方式的数据。

数据库里的 JSON

现代数据库可以直接存 JSON,但这不代表每个字段都该塞进一个巨大的 JSON blob。

实用的模式:

  • 经常用来过滤、连表或排序的稳定字段,存成普通列。
  • 灵活的元数据、事件属性、集成载荷或用户偏好,存成 JSON。
  • 给你频繁查询的那些 JSON 路径建索引。
  • 在应用边界上先校验结构,别把乱七八糟的文档写进去。

各数据库的情况:

  • PostgreSQL 有 jsonjsonb,做索引和包含查询时通常 jsonb 更好。
  • MySQL 有原生 JSON 类型和 JSON_EXTRACT 这类路径函数。
  • SQLite 提供 json_extractjson_each 等 JSON 函数。
  • MongoDB 用 BSON 存文档 —— 一种类 JSON 的二进制格式,还多了些额外类型。

两点提醒:JSON 对象的键顺序不能拿来当业务规则;也不要用重复键去表示多个值 —— 要表示多个值,请用数组。

JSON、XML 与 YAML

JSON、XML 和 YAML 都能传输结构化数据,但在真实项目里手感很不一样。

格式 最适合 代价
JSON API、Web 应用、给程序读的配置 不能写注释、语法严格、数据类型有限
XML 文档标记、命名空间、较老的企业协议 冗长,解析开销更大
YAML 人工编辑的配置 缩进和隐式类型推断容易给人惊喜

同一个用户,用 JSON 表示:

{
  "name": "Alice",
  "age": 30
}

用 XML 表示:

<user>
  <name>Alice</name>
  <age>30</age>
</user>

JSON 在绝大多数 Web API 场景里胜出,因为它紧凑、能干净地映射到常见语言的数据结构,而且浏览器 JavaScript 里就内置了。XML 在偏文档的格式、SOAP、RSS、SAML 以及使用命名空间的系统里依然重要。YAML 对人工编辑配置更友好;但当很多语言和工具需要就"数据到底长什么样"达成一致时,严格 JSON 更稳妥。

常见的 JSON 变体

严格 JSON 是基准线,另外还有几种经常出现的相关格式:

变体 改了什么 怎么解析
JSONC 加了注释,通常也允许尾随逗号 用 JSONC 解析器;别把它当 API 的 JSON 发出去
JSON5 加了更多类 JavaScript 的语法 交换之前先转成严格 JSON
NDJSON / JSON Lines 每行一个 JSON 值 按行切分,逐行解析
JSON Schema 描述合法的 JSON 结构 用 schema 校验器
JSON Pointer 定位 JSON 内部的某个值,比如 /users/0/name 当作路径记法使用,它本身不是 JSON
JSON Patch 描述对 JSON 文档的修改 用 JSON Patch 库来应用这些操作

别把它们当成同一种文件格式。tsconfig.json 之所以能写注释,是因为 TypeScript 工具链是按 JSONC 解析它的;而对外的 API 响应,仍然必须是严格 JSON。

常见的 JSON 错误

载荷解析失败时,我会优先检查这几项:

症状 示例 怎么修
单引号 { 'name': 'Alice' } 改用双引号
没加引号的键 { name: "Alice" } 每个键都加引号
尾随逗号 { "a": 1, } 删掉最后那个逗号
注释 { "a": 1 // note } 删掉注释,或在支持的场合改用 JSONC
Python 字面量 { "ok": True, "x": None } 改成 truenull
undefined { "x": undefined } 要么省略这个键,要么用 null
两份文档挨在一起 {} {} 包进数组,或当成两条独立消息分别解析
字符串里有真实换行 "line one 后面直接换行 改用 \n

粘贴进来的示例,JSON Fix 能在你的浏览器本地修掉常见语法问题。而 API 约定和生产流程,请去修生产方并校验结果,别默默地接受格式错误的数据。

先校验语法,再校验含义

JSON 语法校验回答的问题非常窄:这段文本能被某个严格解析器读进去吗?下面这些命令做的都是这道关卡:

const value = JSON.parse(rawText);
python3 -m json.tool data.json >/dev/null
jq empty data.json

解析不过,就去修语法或者修生产方。解析过了,你手上有了一个 JSON 值,但它未必有用、也未必安全。下面这份载荷在 JSON 语法上完全合法:

{
  "email": "not-an-email",
  "age": -4,
  "role": "owner-of-everything"
}

至于它对你的应用来说合不合法,那是另一个问题 —— 这就轮到 JSON Schema 登场了。

什么是 JSON Schema

JSON Schema 是一份机器可读的 JSON 数据约定。它描述哪些字段必填、允许哪些类型和取值、数组该是什么形状,以及要不要接受未知属性。

这几层值得分清楚:

层次 问题 常用工具
JSON 解析 这是合法的 JSON 文本吗? JSON.parsejson.loadsjq
JSON Schema 解析出来的值符合文档约定的结构吗? Ajv、Python 的 jsonschema
业务校验 这个操作此刻被允许、且有意义吗? 应用代码、数据库、授权规则

JSON Schema 本身也是 JSON。下面这份 Draft 2020-12 的 schema 描述了一条小小的用户记录:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "required": ["id", "email", "role"],
  "properties": {
    "id": {
      "type": "string",
      "pattern": "^usr_[0-9]+$"
    },
    "email": {
      "type": "string",
      "format": "email"
    },
    "role": {
      "enum": ["admin", "editor", "viewer"]
    }
  },
  "additionalProperties": false
}

这个值符合它:

{
  "id": "usr_42",
  "email": "ada@example.com",
  "role": "admin"
}

这份 schema 把好几条原本靠默契的假设变成了可执行的规则:三个字段都必须存在、id 要符合已知的模式、role 只能从固定列表里取,而多出来的字段一律拒绝。

最先会用到的 JSON Schema 关键字

大多数实用的 schema 都从一小撮词汇开始。

关键字 控制什么 常见误区
$schema 使用的 schema 草案或方言 干脆不写,让工具自己猜
type JSON 值的类型 忘了 integer 不包含小数
properties 具名对象字段的规则 以为写了它这些字段就成必填了
required 必须存在的键 重要字段只写在 properties
items 数组元素的规则 忘了约束嵌套对象
enum 一组固定的允许取值 拿它去管一个经常变动的列表
additionalProperties 是否允许未知的对象键 对外响应对象封得太死
$defs$ref 可复用的 schema 片段 改了共享的 $id 却不当成破坏性变更

propertiesrequired 解决的是不同的问题。下面这份 schema 并不要求 email 必填:

{
  "type": "object",
  "properties": {
    "email": { "type": "string" }
  }
}

它只是说:email 如果出现,就必须是字符串。存在性重要的话,请加上 "required": ["email"]

在独立的 JSON Schema 里表示可空值,把两种类型都列出来:

{
  "type": ["string", "null"]
}

另外别以为 default 会帮你把缺失的数据补上。在 JSON Schema 里它只是个注解 —— 除非你的校验器提供了明确的、非标准的写入选项。

在 JavaScript 里做 schema 校验

Ajv 是 JavaScript 和 TypeScript 项目里常用的校验器。用 Draft 2020-12 时,要用配套的构造函数:

npm install ajv ajv-formats
import Ajv2020 from "ajv/dist/2020.js";
import addFormats from "ajv-formats";

const ajv = new Ajv2020({ allErrors: true });
addFormats(ajv);

const schema = {
  $schema: "https://json-schema.org/draft/2020-12/schema",
  type: "object",
  required: ["id", "email"],
  properties: {
    id: { type: "string" },
    email: { type: "string", format: "email" }
  },
  additionalProperties: false
};

const validate = ajv.compile(schema);
const data = { id: "usr_42", email: "ada@example.com" };

if (!validate(data)) {
  console.error(validate.errors);
}

schema 编译一次,然后复用那个校验函数。在表单、测试和开发者工具里,allErrors: true 能一次性给出完整的问题清单,省得别人跑一次改一个字段。

在 Python 里做 schema 校验

Python 的 jsonschema 包支持 Draft 2020-12 的校验器:

pip install jsonschema
from jsonschema import Draft202012Validator, FormatChecker

schema = {
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "type": "object",
    "required": ["id", "email"],
    "properties": {
        "id": {"type": "string"},
        "email": {"type": "string", "format": "email"},
    },
    "additionalProperties": False,
}

data = {"id": "usr_42", "email": "ada@example.com"}
validator = Draft202012Validator(schema, format_checker=FormatChecker())
errors = sorted(validator.iter_errors(data), key=lambda error: error.path)

for error in errors:
    print(list(error.path), error.message)

需要把每个有问题的字段都报出来时,用 iter_errors 比较合适。如果你要的就是"一出错立刻失败",那调一次 validate() 就够了。

schema 校验不等于授权

再完美的 schema,也证明不了某个邮箱真的收得到信、某个用户 ID 真的存在、某个 SKU 真的有货,或者调用方真的有权修改这个账户。它同样不能保证一个字符串放进 HTML 里是安全的。

把 JSON Schema 当作边界检查。之后在应用代码里再补上认证、授权、数据库查询、跨字段规则、内容清洗和副作用管控。结构很重要,但它不是约定的全部。

快速语法检查用 JSON 校验器。输入里混着注释、单引号或尾随逗号时,先用 JSON Fix 修好再套 schema。想看解析后的结构用 JSON 查看器,需要可读输出时看如何格式化 JSON

常见问题

JSON 是什么的缩写?

JSON 是 JavaScript Object Notation 的缩写。虽然名字里有 JavaScript,但它与语言无关 —— JavaScript、Python、Go、Java、Ruby、Rust、PHP、数据库、命令行工具和浏览器都能读写它。

JSON 用来做什么?

API 的请求体和响应体、Webhook、应用配置、包元数据、结构化日志、数据导出、测试数据,以及服务之间的消息,都用 JSON 来写。

JSON 有哪些数据类型?

JSON 有六种值类型:对象、数组、字符串、数字、布尔和 null。它没有日期类型,没有单独的整数类型,没有 undefined,没有函数类型,没有注释类型,也没有 Map、Set 或二进制类型。

JSON 和 JavaScript 对象是一回事吗?

不是。JSON 是文本,语法非常严格;JavaScript 对象字面量是代码。JavaScript 对象字面量支持、而 JSON 不支持的东西包括:注释、尾随逗号、不加引号的键、函数、undefined、Date 对象和计算属性名。

JSON 文件可以以数组开头吗?

可以。JSON 文档可以是任意 JSON 值,数组当然也行 —— 数组、字符串、数字、布尔和 null 都是合法的 JSON。API 响应之所以常用对象,是因为它给元数据留了扩展空间。

JSON 支持注释吗?

不支持。严格 JSON 不允许注释。有些工具在人工编辑的配置里用 JSONC 或 JSON5,但这些文件在通过 API 传输、或交给普通 JSON 解析器之前,需要先转成严格 JSON。

怎么确认 JSON 是合法的?

JSON.parse()python3 -m json.tooljq empty,或者一个严格的 JSON 校验器。如果还要校验必填字段和类型,那就在语法解析之后用 JSON Schema。

JSON 里 null 和"键不存在"有什么区别?

null 表示键在,但值被有意置空。键不存在,则意味着这个字段没被提供、没被请求、不知道,或者不适用。API 约定里应该写清楚自己用的是哪一种。

JSON Schema 是干什么用的?

JSON Schema 用来描述和校验 JSON 约定:必填字段、允许的类型、枚举、数值范围、数组形状、嵌套对象,以及可复用的定义。

写了 properties 字段就变成必填了吗?

不会。properties 只在键存在时施加规则。键必须存在的话,请把它加进 required

format: "email" 一定会拒绝错误的邮箱字符串吗?

不一定。format 的行为取决于校验器及其配置。如果你确实需要它拒绝,请启用 format 断言或校验器的 format 插件 —— 另外别把格式检查和"这个邮箱真能收到信"混为一谈。

JSON Schema 和 TypeScript、OpenAPI 是一回事吗?

不是。TypeScript 的类型是 TypeScript 内部的编译期检查;OpenAPI 是用 JSON Schema 的一种方言来描述 HTTP 接口;而 JSON Schema 本身是一份 JSON 约定 —— 一种描述 JSON 数据结构的格式。

处理 JSON 的工具

参考资料

最后校订于 2026 年 8 月。