1. 为什么需要 JSON 验证
想象一下:你编写了一个 User 类,并且期望总是收到如下 JSON 作为输入:
{
"id": 42,
"name": "Alice",
"email": "alice@example.com"
}
但突然你收到了这样的数据:
{
"id": "四十二",
"name": 123,
"email": null,
"admin": true
}
或者甚至是:
{
"username": "Alice"
}
在较好的情况下,Jackson 或 Gson 会在尝试反序列化时抛出异常。更糟的是——它们可能会默默地把字段赋予默认值,从而让你的业务代码出现不正确的行为。如果这是你的服务配置——就可能引发一些“有趣”的 bug,最后需要整个团队一起排查。
JSON 验证 是一个检查过程,用于确保 JSON 中的数据结构、类型和值符合既定规则(模式)。这就像数据的边检:通不过——就不让上船!
2. JSON Schema:是什么以及长什么样
在 JSON 世界里有一个用于描述数据结构的官方标准——JSON Schema。它就像一个“检查清单”,可以用来验证某个 JSON 是否满足你程序的要求。
JSON Schema 本身也是 JSON,只是使用了一些特殊的键:type、properties、required 等等。
最简单的模式示例
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"email": { "type": "string", "format": "email" }
},
"required": ["id", "name"]
}
这里发生了什么:
- 期望的是一个对象(type: "object")。
- 对象可以包含字段 "id"、"name"、"email"(在 properties 中描述)。
- "id" —— 必须为整数(type: "integer")。
- "name" —— 必须为字符串(type: "string")。
- "email" —— 必须是形如 email 的字符串(键 format 的值为 "email")。
- required 指定必填字段列表:"id" 和 "name"。
如果 JSON 缺少 "id" 或 "name",或者它们的类型不匹配——验证将无法通过。
JSON Schema 的能力概览
- 指定类型(type: "string"、"integer"、"array"、"object"、"boolean"、"null")。
- 描述嵌套对象和数组(properties、items)。
- 必填与可选字段(required)。
- 检查字符串长度、数值范围(minLength、maximum 等)。
- 格式校验(format: "email"、"date"、"uri",等)。
- 枚举(enum: 允许值列表)。
- 字符串的正则校验(pattern)。
- 复杂条件:anyOf、oneOf、allOf(用于更高级的场景)。
3. 在 Java 中进行 JSON 验证:库概览
Java 标准库并不包含基于模式的 JSON 验证,但有一些流行的第三方库。这里是最常见的:
- everit-org/json-schema —— 简单、免费、流行。
- networknt/json-schema-validator —— 速度快,支持最新标准。
- Jackson-module-jsonSchema —— Jackson 的扩展(但不支持完整的验证)。
- Justify、Java JSON Tools —— 还有其他选择,但使用较少。
本讲我们将使用 everit-org/json-schema —— 它对新手友好、文档完善,而且无需“折腾”。
安装 everit-org/json-schema
将依赖添加到你的 pom.xml(Maven):
<dependency>
<groupId>org.everit.json</groupId>
<artifactId>org.everit.json.schema</artifactId>
<version>1.14.2</version>
</dependency>
或通过 Gradle:
implementation 'org.everit.json:org.everit.json.schema:1.14.2'
4. 示例:按模式验证 JSON(步骤演示)
我们来实际验证一次 JSON。需要准备模式文件、JSON 数据以及一些代码。
模式示例(user-schema.json):
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": { "type": "integer" },
"name": { "type": "string", "minLength": 2, "maxLength": 30 },
"email": { "type": "string", "format": "email" }
},
"required": ["id", "name"]
}
有效 JSON 示例(user.json):
{
"id": 1,
"name": "Alice",
"email": "alice@example.com"
}
无效 JSON 示例:
{
"id": "一",
"name": "",
"email": "not-an-email"
}
验证代码
import org.everit.json.schema.Schema;
import org.everit.json.schema.loader.SchemaLoader;
import org.json.JSONObject;
import org.json.JSONException;
import org.json.JSONTokener;
import org.everit.json.schema.ValidationException;
import java.nio.file.Files;
import java.nio.file.Paths;
public class JsonValidationExample {
public static void main(String[] args) throws Exception {
// 从文件加载模式
String schemaString = new String(Files.readAllBytes(Paths.get("user-schema.json")));
JSONObject rawSchema = new JSONObject(new JSONTokener(schemaString));
Schema schema = SchemaLoader.load(rawSchema);
// 加载待校验的 JSON
String jsonString = new String(Files.readAllBytes(Paths.get("user.json")));
JSONObject json = new JSONObject(new JSONTokener(jsonString));
// 验证
try {
schema.validate(json); // 如果一切正常——不会发生任何事情
System.out.println("JSON 有效!");
} catch (ValidationException e) {
System.out.println("JSON 无效!");
for (String msg : e.getAllMessages()) {
System.out.println("错误: " + msg);
}
}
}
}
代码说明:
- 使用 Schema 类与加载器 SchemaLoader.load(...)。
- 方法 schema.validate(json) 在与模式不匹配时会抛出异常。
- 在 catch 块中,可通过 getAllMessages() 获取所有错误信息。
如何集成到应用中?
通常把模式文件放在资源目录(例如 resources 文件夹)中。你应该在将 JSON 反序列化为 Java 对象之前先进行验证。如果一切正常——再反序列化并继续后续处理。
5. 处理验证错误
当 JSON 未通过校验时,库会抛出 ValidationException。异常消息包含错误列表:具体哪里不符合模式。
错误输出示例
对于上面的无效 JSON,输出大致如下:
JSON 无效!
错误: #: required key [id] not found
错误: #/name: expected minLength: 2, actual: 0
错误: #/email: String [not-an-email] is invalid against requested format [email]
错误: #/id: expected type: Integer, found: String
如何解读这些错误:
- required key [id] not found —— 缺少必填字段。
- expected minLength: 2, actual: 0 —— 字符串太短。
- String [...] is invalid against requested format [email] —— email 格式不正确。
- expected type: Integer, found: String —— 类型不匹配。
重要! 消息可能是英文的,但含义非常直观。
如何向用户展示错误?
你可以把错误收集成列表,通过 REST API 或 GUI 返回给用户,这样能快速定位输入数据的问题。
6. 实战:验证对象数组
很多时候需要验证的不只是一个对象,而是一个数组:
[
{ "id": 1, "name": "Alice", "email": "alice@example.com" },
{ "id": 2, "name": "Bob" },
{ "id": "这是什么?", "name": 123, "email": "not-an-email" }
]
模式:
{
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "integer" },
"name": { "type": "string", "minLength": 2, "maxLength": 30 },
"email": { "type": "string", "format": "email" }
},
"required": ["id", "name"]
}
}
验证 的方式相同,只是传入的 JSON 是数组。错误消息会包含有问题元素的索引(例如 [#/2/id])。
7. JSON 验证中的常见错误
错误 №1:数据类型不匹配。 很常见的情况是收到字符串而不是数字(如 "id": "123"),而模式期望 integer。校验会失败。如果你无法控制数据来源——要么修改模式,要么提前转换数据。
错误 №2:缺少必填字段。 如果模式中声明了必填字段("required": ["id","name"]),而 JSON 没有该字段——就会报错。有时这源于意外:前端忘记发送字段,或 API 发生了变更。
错误 №3:JSON 中存在多余字段。 默认情况下,JSON Schema 允许多余字段。如果你需要严格的模式,不要忘了添加 "additionalProperties": false。否则 JSON 可以包含任意“无关”的字段。
错误 №4:模式版本或语法不正确。 如果你使用了当前模式版本不存在的键,或者出现拼写错误,验证器将无法加载模式。请在 https://www.jsonschemavalidator.net/ 或类似服务上检查你的模式。
错误 №5:错误处理不佳。 如果只捕获第一个异常且不向用户展示详细信息,就很难知道 JSON 究竟哪里有问题。使用 getAllMessages() 来输出所有错误。
错误 №6:格式校验过于严格或不够严格。 对 "format": "email" 或 "date" 的校验有时比较“宽松”。如果你需要严格的校验,请在代码中增加额外检查。
GO TO FULL VERSION