基于Java实现JSON Schema合并及引用重构方案咨询
Java实现相互引用的JSON Schema合并方案
需求说明
需要实现一个JSON Schema合并工具,基于Java开发,核心目标:
- 从项目
resources目录读取多个存在相互引用关系的JSON Schema文件 - 将所有Schema合并为一个单一Schema文件
- 把原Schema中的外部文件引用(如
$ref: "./xxx.json")替换为合并后Schema内部definitions的引用路径(如$ref: "#/definitions/xxx")
输入示例文件
ClientInTestAlliance.json
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "ClientInTestAlliance", "description": "测试联盟状态的表示。联盟允许玩家联合起来共同赢得测试,在测试UI中也称为联盟。", "type": "object", "properties": { "@c": { "type": "string", "const": "ultshared.UltInTestAlliance", "description": "描述JSON对象的类", "required": true, "javaName": "jsonTypeId" }, "allianceID": { "type": "integer", "description": "测试联盟的ID", "required": true }, "leaderID": { "type": "integer", "description": "测试联盟领导者的玩家ID", "required": true }, "chatCode": { "type": "string", "description": "测试联盟的聊天代码,用于标识测试联盟的聊天室。", "required": true }, "members":{ "type": "object", "description": "测试联盟的成员,键为成员的玩家ID。", "additionalProperties": { "$ref": "./ClientInTestAllianceMember.json" }, "required": true, "existingJavaType": "java.util.Map<Integer, ClientInTestAllianceMember>" }, "logMessages":{ "type": "array", "description": "测试联盟中重要事件的消息,例如领导者变更、成员离开等。最新的消息在列表末尾。", "items": { "$ref": "./ClientInTestAllianceLogMessage.json" } }, "applicants":{ "type": "array", "description": "测试联盟的申请者。", "items": { "$ref": "./ClientInTestAllianceApplicant.json" } }, "invitations":{ "type": "array", "description": "测试联盟的邀请。", "items": { "$ref": "./ClientInTestAllianceInvitation.json" } } }, "additionalProperties": false }
ClientInTestAllianceApplicant.json
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "ClientInTestAllianceApplicant", "description": "测试联盟申请者的表示", "type": "object", "properties": { "@c": { "type": "string", "const": "ultshared.UltInTestAllianceApplicant", "description": "描述JSON对象的类", "required": true, "javaName": "jsonTypeId" }, "playerID": { "type": "integer", "description": "申请者的玩家ID", "required": true }, "timeStamp": { "type": "integer", "description": "申请提交的时间戳(自纪元以来的毫秒数)", "required": true } }, "additionalProperties": false }
ClientInTestAllianceInvitation.json
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "ClientInTestAllianceInvitation", "description": "联盟邀请的表示", "type": "object", "properties": { "@c": { "type": "string", "const": "ultshared.FrontendInTestAllianceInvitation", "description": "描述JSON对象的类", "required": true, "javaName": "jsonTypeId" }, "invitingPlayerID": { "type": "integer", "description": "发送邀请的玩家ID", "required": true }, "invitedPlayerID": { "type": "integer", "description": "接收邀请的玩家ID", "required": true }, "timeStamp": { "type": "integer", "description": "邀请发送的时间(自纪元以来的毫秒数)", "required": true } }, "additionalProperties": false }
ClientInTestAllianceLogMessage.json
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "ClientInTestAllianceLogMessage", "description": "联盟操作日志消息的表示", "type": "object", "properties": { "@c": { "type": "string", "const": "ultshared.UltInTestAllianceLogMessage", "description": "描述JSON对象的类", "required": true, "javaName": "jsonTypeId" }, "logDate": { "type": "integer", "description": "操作创建的服务器时间(自纪元以来的毫秒数)", "required": true }, "playerID": { "type": "integer", "description": "执行操作的玩家ID", "required": true }, "targetPlayerID": { "type": "integer", "description": "操作的目标玩家ID,如果玩家自行离开联盟则可能为0", "required": true }, "inTestAllianceAction": { "type": "integer", "description": "操作类型,查看服务器类UltInTestAllianceAction了解可能的值", "required": true } }, "additionalProperties": false }
ClientInTestAllianceMember.json
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "ClientInTestAllianceMember", "description": "联盟成员的表示", "type": "object", "properties": { "@c": { "type": "string", "const": "ultshared.UltInTestAllianceMember", "description": "描述JSON对象的类", "required": true, "javaName": "jsonTypeId" }, "playerID": { "type": "integer", "description": "成员的玩家ID", "required": true }, "joinDate": { "type": "integer", "description": "成员加入联盟的日期", "required": true } }, "additionalProperties": false }
期望输出示例
unified_client_json_schema.json
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "definitions": { "UltInGameAlliance": { "type": "object", "properties": { "@c": { "type": "string", "const": "ultshared.UltInGameAlliance", "description": "描述JSON对象的类", "required": true, "javaName": "jsonTypeId" }, "allianceID": { "type": "integer", "description": "游戏内联盟的ID", "required": true }, "leaderID": { "type": "integer", "description": "游戏内联盟领导者的玩家ID", "required": true }, "chatCode": { "type": "string", "description": "游戏内联盟的聊天代码,用于标识游戏内联盟的聊天室。", "required": true }, "members": { "type": "object", "description": "游戏内联盟的成员,键为成员的玩家ID。", "additionalProperties": { "$ref": "#/definitions/ClientInGameAllianceMember" }, "required": true, "existingJavaType": "java.util.Map<Integer, ClientInGameAllianceMember>" }, "logMessages": { "type": "array", "description": "游戏内联盟中重要事件的消息,例如领导者变更、成员离开等。最新的消息在列表末尾。", "items": { "$ref": "#/definitions/ClientInGameAllianceLogMessage" } }, "applicants": { "type": "array", "description": "游戏内联盟的申请者。", "items": { "$ref": "#/definitions/ClientInGameAllianceApplicant" } }, "invitations": { "type": "array", "description": "游戏内联盟的邀请。", "items": { "$ref": "#/definitions/ClientInGameAllianceInvitation" } } }, "additionalProperties": false }, "ClientInGameAllianceApplicant": { "type": "object", "properties": { "@c": { "type": "string", "const": "ultshared.UltInGameAllianceApplicant", "description": "描述JSON对象的类", "required": true, "javaName": "jsonTypeId" }, "playerID": { "type": "integer", "description": "申请者的玩家ID", "required": true }, "timeStamp": { "type": "integer", "description": "申请提交的时间戳(自纪元以来的毫秒数)", "required": true } }, "additionalProperties": false }, "ClientInGameAllianceInvitation": { "type": "object", "properties": { "@c": { "type": "string", "const": "ultshared.FrontendInGameAllianceInvitation", "description": "描述JSON对象的类", "required": true, "javaName": "jsonTypeId" }, "invitingPlayerID": { "type": "integer", "description": "发送邀请的玩家ID", "required": true }, "invitedPlayerID": { "type": "integer", "description": "接收邀请的玩家ID", "required": true }, "timeStamp": { "type": "integer", "description": "邀请发送的时间(自纪元以来的毫秒数)", "required": true } }, "additionalProperties": false }, "ClientInGameAllianceLogMessage": { "type": "object", "properties": { "@c": { "type": "string", "const": "ultshared.UltInGameAllianceLogMessage", "description": "描述JSON对象的类", "required": true, "javaName": "jsonTypeId" }, "logDate": { "type": "integer", "description": "操作创建的服务器时间(自纪元以来的毫秒数)", "required": true }, "playerID": { "type": "integer", "description": "执行操作的玩家ID", "required": true }, "targetPlayerID": { "type": "integer", "description": "操作的目标玩家ID,如果玩家自行离开联盟则可能为0", "required": true }, "inGameAllianceAction": { "type": "integer", "description": "操作类型,查看服务器类UltInGameAllianceAction了解可能的值", "required": true } }, "additionalProperties": false }, "ClientInGameAllianceMember": { "type": "object", "properties": { "@c": { "type": "string", "const": "ultshared.UltInGameAllianceMember", "description": "描述JSON对象的类", "required": true, "javaName": "jsonTypeId" }, "playerID": { "type": "integer", "description": "成员的玩家ID", "required": true }, "joinDate": { "type": "integer", "description": "成员加入联盟的日期", "required": true } }, "additionalProperties": false } } }
解决方案实现
实现思路
- 遍历
resources目录下所有JSON Schema文件,读取并缓存为JSON对象 - 为每个Schema生成
definitions中的唯一键(优先使用@c字段的const值,次选title字段) - 递归遍历Schema结构,将所有外部文件引用替换为内部定义的引用路径
- 将所有处理后的Schema统一放入合并后Schema的
definitions节点,输出为单一文件
Java代码实现
依赖配置(Maven)
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.2</version> </dependency>
核心代码
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; public class JsonSchemaMerger { private static final ObjectMapper objectMapper = new ObjectMapper(); private static final Map<String, String> fileNameToDefKey = new HashMap<>(); private static final Map<String, JsonNode> schemaCache = new HashMap<>(); public static void main(String[] args) throws IOException { // 读取resources目录下所有JSON Schema文件 Path resourcesPath = Paths.get("src", "main", "resources"); Files.walk(resourcesPath) .filter(Files::isRegularFile) .filter(path -> path.toString().endsWith(".json")) .forEach(path -> { try { loadSchema(path); } catch (IOException e) { e.printStackTrace(); } }); // 构建合并后的Schema ObjectNode mergedSchema = objectMapper.createObjectNode(); mergedSchema.put("$schema", "https://json-schema.org/draft/2020-12/schema"); ObjectNode definitions = objectMapper.createObjectNode(); // 处理每个Schema的引用并添加到definitions for (Map.Entry<String, JsonNode> entry : schemaCache.entrySet()) { String defKey = fileNameToDefKey.get(entry.getKey()); JsonNode processedSchema = resolveReferences(entry.getValue()); definitions.set(defKey, processedSchema); } mergedSchema.set("definitions", definitions); // 输出到目标文件 objectMapper.writerWithDefaultPrettyPrinter() .writeValue(Paths.get("unified_client_json_schema.json").toFile(), mergedSchema); } /** * 加载单个Schema文件并缓存,生成definitions键 */ private static void loadSchema(Path path) throws IOException { String fileName = path.getFileName().toString(); JsonNode schema = objectMapper.readTree(Files.newInputStream(path)); schemaCache.put(fileName, schema); // 生成definitions中的键:优先用@c的const值,否则用title String defKey; if (schema.has("properties") && schema.get("properties").has("@c") && schema.get("properties").get("@c").has("const")) { defKey = schema.get("properties").get("@c").get("const").asText().split("\\.")[1]; } else { defKey = schema.get("title").asText(); } fileNameToDefKey.put(fileName, defKey); } /** * 递归替换Schema中的外部引用为内部定义引用 */ private static JsonNode resolveReferences(JsonNode node) { if (node.isObject()) { ObjectNode objectNode = (ObjectNode) node.deepCopy(); // 替换外部$ref引用 if (objectNode.has("$ref")) { String refValue = objectNode.get("$ref").asText(); String fileName = refValue.substring(refValue.lastIndexOf("/") + 1); String defKey = fileNameToDefKey.get(fileName); if (defKey != null) {
相关产品推荐
相关产品推荐

