【问题标题】:Java: Deserialize composite JSON Schema with $ref to one single schemaJava:使用 $ref 将复合 JSON Schema 反序列化为一个单一模式
【发布时间】:2020-04-23 12:47:52
【问题描述】:

根据Structuring a complex schema,可能有如下关系:

  1. 基本 JSON 架构 (customer.json)
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "billing_address": { "$ref": "address.json" }
  }
}

  1. 引用的 JSON 架构 (address.json)
{
  "type": "object",
  "properties": {
    "street_address": { "type": "string" },
    "city":           { "type": "string" },
    "state":          { "type": "string" }
  },
  "required": ["street_address", "city", "state"]
}

这种方法的主要优点是可重用性。

如果我想将这些架构组合成一个,则会出现问题。例如,我需要为所有支持的字段生成一个包含虚拟值的 JSON 文件。

所以,我希望得到这个架构:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "billing_address": { 
       "street_address": { "type": "string" },
       "city":           { "type": "string" },
       "state":          { "type": "string" }
    }
  }
}

请注意,所有模式都存在于类路径中。

我一直在寻找有关如何在 Java 中执行此操作的现有解决方案。 不幸的是,大多数库都解决了如何通过 POJO 生成模式的任务。但在这种情况下,我需要相反的

【问题讨论】:

  • 您的预期结果与给定部分不等价。在 billing_address 下应该有另一个 propertiesstreet_address/city/state 被列出之前 - 但你的意图很明确。

标签: java json jackson jsonschema


【解决方案1】:

两个方向都有生成器:

  • 从 POJO 到架构
  • 从架构到 POJO

你似乎对两者都不感兴趣,因为你想要的是:

  • 从架构(部分)到(单个)架构

恐怕您找到现有解决方案的机会可能很小。
但是你应该可以自己做这件事,特别是如果你可以做一些简化的假设:

  1. 您的数据模型中的任何地方都没有名称为 $ref 的属性。
  2. 所有模式部分都存在于类路径中——为简单起见:在与执行的 java 类相同的包中 单独架构部分的合并。
  3. 没有从其引用的其他架构部分之一对您的主/条目架构的循环引用。
  4. 可以在条目架构的definitions 中包含不同部分。
  5. 架构部分没有重叠的definitions

该实用程序可能看起来像这样:

public class SchemaMerger {

    private final ObjectMapper objectMapper = new ObjectMapper();
    private final Map<String, ObjectNode> schemas = new HashMap<>();
    private final List<ObjectNode> definitions = new ArrayList<>();

    public String createConsolidatedSchema(String entrySchemaPath) throws IOException {
        ObjectNode entrySchema = this.getSchemaWithResolvedParts(entrySchemaPath);
        ObjectNode consolidatedSchema = this.objectMapper.createObjectNode().setAll(entrySchema);
        ObjectNode definitionsNode = consolidatedSchema.with("definitions");
        this.definitions.forEach(definitionsNode::setAll);
        for (Map.Entry<String, ObjectNode> schemaPart : this.schemas.entrySet()) {
            // include schema loaded from separate file in definitions
            definitionsNode.set(schemaPart.getKey(), schemaPart.getValue().without("$schema"));
        }
        return consolidatedSchema.toPrettyString();
    }

    private ObjectNode getSchemaWithResolvedParts(String schemaPath) throws IOException {
        ObjectNode entrySchema = (ObjectNode) this.objectMapper.readTree(SchemaMerger.loadResource(schemaPath));
        this.resolveExternalReferences(entrySchema);
        JsonNode definitionsNode = entrySchema.get("definitions");
        if (definitionsNode instanceof ObjectNode) {
            this.definitions.add((ObjectNode) definitionsNode);
            entrySchema.remove("definitions");
        }
        return entrySchema;
    }

    private void resolveExternalReferences(JsonNode schemaPart) throws IOException {
        if (schemaPart instanceof ObjectNode || schemaPart instanceof ArrayNode) {
            // recursively iterate over all nested nodes
            for (JsonNode field : schemaPart) {
                this.resolveExternalReferences(field);
            }
        }
        if (!(schemaPart instanceof ObjectNode)) {
            return;
        }
        JsonNode reference = schemaPart.get("$ref");
        if (reference instanceof TextNode) {
            String referenceValue = reference.textValue();
            if (!referenceValue.startsWith("#")) {
                // convert reference to separate file to entry in definitions
                ((ObjectNode) schemaPart).put("$ref", "#/definitions/" + referenceValue);
                if (!this.schemas.containsKey(referenceValue)) {
                    this.schemas.put(referenceValue, this.getSchemaWithResolvedParts(referenceValue));
                }
            }
        }
    }

    private static String loadResource(String resourcePath) throws IOException {
        StringBuilder stringBuilder = new StringBuilder();
        try (InputStream inputStream = SchemaMerger.class.getResourceAsStream(resourcePath);
                Scanner scanner = new Scanner(inputStream, StandardCharsets.UTF_8.name())) {
            while (scanner.hasNext()) {
                stringBuilder.append(scanner.nextLine()).append('\n');
            }
        }
        return stringBuilder.toString();
    }
}

调用new SchemaMerger().createConsolidatedSchema("customer.json") 会生成以下架构:

{
  "$schema" : "http://json-schema.org/draft-07/schema#",
  "type" : "object",
  "properties" : {
    "billing_address" : {
      "$ref" : "#/definitions/address.json"
    }
  },
  "definitions" : {
    "address.json" : {
      "type" : "object",
      "properties" : {
        "street_address" : {
          "type" : "string"
        },
        "city" : {
          "type" : "string"
        },
        "state" : {
          "type" : "string"
        }
      },
      "required" : [ "street_address", "city", "state" ]
    }
  }
}

这应该为您提供构建所需内容的起点。

【讨论】:

  • 这是一个相当不错的解决方案,只需稍加改进即可使用。我也有类似的想法。 @yevtsy,这个答案对你有帮助吗?
【解决方案2】:

参考:this post。我已经发布了一个可能的解决方案。

尽管如前所述,我自己还没有尝试过。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-06
    • 1970-01-01
    相关资源
    最近更新 更多