面向开发者的 JSON:语法、验证和常见错误

· 12分钟阅读

目录

什么是 JSON 以及为什么它很重要

JSON(JavaScript Object Notation)已成为网络数据交换的事实标准。尽管其名称暗示与 JavaScript 有关,但 JSON 是完全独立于语言的,几乎所有现代编程语言都支持它。

JSON 最初由 Douglas Crockford 在 2000 年代初期规范,作为 XML 的轻量级替代方案出现。其简洁性和人类可读性使其成为 API、配置文件和数据存储的完美格式。

如今,JSON 为从 REST API 和 NoSQL 数据库到配置文件和数据管道的一切提供支持。深入理解 JSON 不仅有帮助——它对现代软件开发至关重要。

快速提示:JSON 不是编程语言或数据库格式。它纯粹是一种数据序列化格式——一种将结构化数据表示为文本的方式。

JSON 语法规则

JSON 的语法看似简单,但其严格性让许多开发者措手不及。与 JavaScript 对象不同,JSON 对语法变化零容忍。

这是一个演示核心结构的有效 JSON 示例:

{
  "name": "RunDev",
  "version": 2,
  "features": ["formatter", "validator", "converter"],
  "config": {
    "theme": "dark",
    "autoSave": true
  },
  "deprecated": null
}

不可协商的规则

每个 JSON 文档都必须遵循这些严格规则:

为什么如此严格?

JSON 的严格性是有意为之。通过消除歧义,JSON 确保任何有效的 JSON 文档都可以在所有平台和语言中以相同方式解析。这种可预测性使 JSON 在数据交换中可靠。

缺少注释经常受到批评,但它迫使开发者通过清晰的键名和结构使数据自文档化。对于需要注释的配置文件,考虑使用 JSONC(带注释的 JSON)或 JSON5。

专业提示:使用我们的 JSON 格式化工具自动修复常见语法错误,并确保您的 JSON 在部署前有效。

理解 JSON 数据类型

JSON 恰好支持六种数据类型。这个有限的集合保持了格式的简单性,同时涵盖了大多数数据表示需求。

类型 示例 注释
字符串 "hello world" 必须使用双引号。支持转义序列:\n\t\u0041\"\\
数字 423.14-11e10 不区分整数和浮点数。无十六进制、八进制或特殊值如 Infinity
布尔值 truefalse 仅小写。"true" 是字符串,不是布尔值
Null null 仅小写。表示有意缺少值
数组 [1, "two", true] 有序列表。可以包含混合类型(尽管同质数组更清晰)
对象 {"key": "value"} 无序键值对。键必须是字符串且应唯一

字符串和转义序列

JSON 字符串支持几种特殊字符的转义序列:

多行字符串必须使用转义序列——不允许字面换行:

{
  "correct": "Line 1\nLine 2\nLine 3",
  "incorrect": "Line 1
Line 2
Line 3"
}

数字:允许什么和不允许什么

JSON 数字比大多数编程语言更具限制性:

JSON 规范没有定义精度限制,但大多数实现使用 IEEE 754 双精度浮点数,这意味着整数在 2^53 - 1(9,007,199,254,740,991)之前是安全的。

专业提示:对于超出 JavaScript 安全整数范围的大整数,考虑将它们存储为字符串以防止解析期间的精度损失。

对象和键唯一性

虽然 JSON 规范规定对象键应该是唯一的,但它没有规定解析器应如何处理重复项。不同的实现行为不同:

最佳实践:始终确保您的键是唯一的,以避免不同解析器之间的不可预测行为。

常见错误及如何避免

即使是经验丰富的开发者也会犯 JSON 语法错误。以下是最常见的错误及其修复方法:

错误 错误示例 正确示例
单引号 {'name': 'test'} {"name": "test"}
尾随逗号 {"a": 1, "b": 2,} {"a": 1, "b": 2}
未加引号的键 {name: "test"} {"name": "test"}
注释 {"a": 1 // comment} 删除注释或使用 JSONC
Undefined 值 {"a": undefined} {"a": null}
前导零 {"port": 080} {"port": 80}
字面换行 "line1
line2"
"line1\nline2"
函数值 {"fn": function(){}} 不支持——使用字符串或重构

JavaScript 对象陷阱

JSON 错误的最大来源是将其视为 JavaScript 对象字面量语法。它们看起来相似但有关键区别:

// 有效的 JavaScript 对象
const jsObj = {
  name: 'test',        // 未加引号的键,单引号
  age: 30,
  active: true,
  getData: function() { return this.name; },  // 允许函数
  // 允许注释
};

// 有效的 JSON(作为字符串)
const jsonStr = `{
  "name": "test",
  "age": 30,
  "active": true
}`;

记住:JSON 是一种数据格式,不是代码。它不能包含函数、注释或可执行逻辑。

字符编码问题

JSON 必须以 UTF-8、UTF-16 或 UTF-32 编码。UTF-8 是最常见和推荐的编码。注意:

快速提示:将您的 JSON 粘贴到我们的 JSON 验证器中,立即识别语法错误、编码问题和结构问题。

验证 JSON:工具和技术

验证对于在运行时失败之前捕获错误至关重要。以下是如何在不同环境中验证 JSON。

命令行验证

使用内置工具快速验证:

# Python(内置,随处可用)
python3 -m json.tool data.json

# 美化打印和验证
python3 -m json.tool data.json output.json

# jq(强大的 JSON 处理器)
jq . data.json

# jq 带错误详情
jq . data.json || echo "Invalid JSON"

# Node.js 单行命令
node -e "console.log(JSON.parse(require('fs').readFileSync('data.json')))"

# 使用 jsonlint(通过 npm 安装)
jsonlint data.json

程序化验证

在应用程序代码中验证 JSON:

// JavaScript/Node.js
function isValidJSON(str) {
  try {
    JSON.parse(str);
    return true;
  } catch (e) {
    console.error('JSON Error:', e.message);
    return false;
  }
}

# Python
import json

def is_valid_json(json_str):
    try:
        json.loads(json_str)
        return True
    except json.JSONDecodeError as e:
        print(f'JSON Error: {e}')
        return False

// Go
import "encoding/json"

func isValidJSON(data []byte) bool {
    var js json.RawMessage
    return json.Unmarshal(data, &js) == nil
}

模式验证

语法验证仅检查 JSON 是否格式良好。模式验证确保数据结构和类型符合您的要求。

JSON Schema 是定义 JSON 结构的标准:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1
    },
    "age": {
      "type": "integer",
      "minimum": 0,
      "maximum": 150
    },
    "email": {
      "type": "string",
      "format": "email"
    }
  },
  "required": ["name", "email"]
}

流行的模式验证库:

专业提示:使用我们的 JSON Schema 验证器在代码中实现之前针对示例数据测试您的模式。

在线验证工具

用于开发期间的快速检查:

解析和序列化最佳实践

解析将 JSON 文本转换为原生数据结构。序列化则相反。这两种操作都有性能和安全影响。

安全解析实践

始终在错误处理中包装 JSON 解析:

// JavaScript - 基本解析
try {
  const data = JSON.parse(jsonString);
  // 使用数据
} catch (error) {
  console.error('Failed to parse JSON:', error.message);
  // 适当处理错误
}

// JavaScript - 使用 reviver 函数
const data = JSON.parse(jsonString, (key, value) => {
  // 从字符串转换日期
  if (key.endsWith('Date') && typeof value === 'string') {
    return new Date(value);
  }
  return value;
});

序列化最佳实践

控制对象如何转换为 JSON:

// JavaScript - 基本序列化
const json = JSON.stringify(data);

// 带缩进的美化打印
const json = JSON.stringify(data, null, 2);

// 使用 replacer 的自定义序列化
const json = JSON.stringify(data, (key, value) => {
  // 删除敏感字段
  if (key === 'password' || key === 'apiKey') {
    return undefined;
  }
  // 将 BigInt 转换为字符串
  if (typeof value === 'bigint'