Specification scope: Core with the Import companion specification.

Tool: Structurize is the JSON Structure-focused command-line interface and conversion toolkit from the Avrotize project.

A converter can project only the types it has loaded. If a JSON Structure document refers to definitions supplied through $import or $importdefs, the converter needs the resolved type graph, not a promise that another document exists somewhere.

That ordering matters because Structurize does not process either import keyword in the tests reported here. Its JSON Structure converters resolve local references, but they do not fetch imported documents, copy their definitions, rewrite their internal pointers, or apply import shadowing. The correct pipeline therefore has two processors: first resolve and flatten imports with a conforming JSON Structure processor; then pass the closed schema to an s2... converter.

The source graph comes first

Consider a fulfillment schema that keeps shared postal types in a separate document:

{
  "$schema": "https://json-structure.org/meta/extended/v0/#",
  "$id": "https://schemas.example.com/fulfillment/order/v1",
  "name": "OrderDocument",
  "$uses": ["JSONStructureImport"],
  "$root": "#/definitions/Order",
  "definitions": {
    "Shared": {
      "$importdefs": "https://schemas.example.com/fulfillment/shared/v1"
    },
    "Order": {
      "name": "Order",
      "type": "object",
      "properties": {
        "orderId": { "type": "string" },
        "shipTo": { "type": { "$ref": "#/definitions/Shared/PostalAddress" } }
      },
      "required": ["orderId", "shipTo"]
    }
  }
}

The local $ref is not enough by itself. It points into the namespace where an import processor must place PostalAddress. Until that placement has happened, the source graph has a hole in it.

The JSON Structure import draft defines the missing operation. It distinguishes importing a complete schema with $import from importing only its definitions with $importdefs. It also defines namespace placement, internal pointer rewriting, and local shadowing. Those are schema semantics, not file concatenation rules.

The following flattened document is illustrative. Producing it requires a conforming JSON Structure import processor; no current Structurize converter performs this step.

{
  "$schema": "https://json-structure.org/meta/core/v0/#",
  "$id": "https://schemas.example.com/fulfillment/order/v1/resolved",
  "name": "OrderDocument",
  "type": "object",
  "properties": {
    "orderId": { "type": "string" },
    "shipTo": { "type": { "$ref": "#/definitions/Shared/PostalAddress" } }
  },
  "required": ["orderId", "shipTo"],
  "definitions": {
    "Shared": {
      "PostalAddress": {
        "name": "PostalAddress",
        "type": "object",
        "properties": {
          "street": { "type": "string" },
          "city": { "type": "string" },
          "countryCode": { "type": "string" }
        },
        "required": ["street", "city", "countryCode"]
      }
    }
  }
}

Here Shared is a namespace object and PostalAddress is the named type within it. The resolver has also materialized the selected Order root as the top-level object because the Parquet converter requires top-level type: "object"; a document that only identifies its root with $root is rejected. The slash separates JSON Pointer path segments; it is not part of an identifier. The graph is closed: every reference needed for projection resolves within the supplied artifact.

Run the converter after flattening

Once a conforming processor has written order.resolved.struct.json, validate that artifact as a closed JSON Structure schema before invoking Structurize. Normal Structurize commands can then consume the validated boundary artifact:

structurize s2sql order.resolved.struct.json `
  --dialect postgres `
  --out generated/order.sql

structurize s2pq order.resolved.struct.json `
  --format schema `
  --out generated/order.parquet.schema.json
Generated output: order.sql
CREATE TABLE "OrderDocument" (
    "orderId" VARCHAR(512),
    "shipTo" VARCHAR(512),
    PRIMARY KEY ("orderId", "shipTo")
);

The first command projects the closed JSON Structure graph into PostgreSQL DDL. The second command does not complete in Structurize: the nested reference reaches a converter path that fails with unhashable type: 'dict'. Closure removes the import dependency, but it does not guarantee that every target converter supports the resulting local-reference shape. Keep this command as a capability test and do not claim a Parquet artifact when it fails.

The unresolved source is a useful negative test. In the tested setup, Structurize does not reject it: s2sql exits successfully and emits shipTo VARCHAR(512). The missing PostalAddress type has disappeared rather than producing an import or reference error. Validate closure before conversion; converter success does not establish it.

This separation also gives a build a useful inspection point. Check in or retain the resolved schema as an artifact, record the resolver version and source URIs, and compare the flattened graph when an upstream library changes. A projection diff then answers a narrower question: what did the target mapping change? Without that boundary, an import update and a converter update become one undifferentiated surprise.

The Structurize implementation makes the current boundary visible in its commands.json and JSON Structure converter modules at the linked source tree. They accept a schema file and resolve references from the loaded document. They contain no implementation of the import draft’s retrieval and copying algorithm.

Source imports are not target imports

Some projection targets have their own import or include mechanisms. Protocol Buffers has import; SQL tools may split DDL across scripts; programming languages have modules and packages. Those mechanisms organize generated artifacts in the target system. They do not resolve JSON Structure source imports.

A converter may choose to emit several target files and connect them with target-native imports. That happens after the source graph is known. It must not translate $importdefs mechanically into a target import, because the two operations have different namespace, shadowing, identity, and retrieval rules. Matching vocabulary is not matching semantics.

Use this pipeline:

  1. Retrieve source documents according to the JSON Structure import rules.
  2. Apply namespace placement, pointer rewriting, recursion limits, and shadowing.
  3. Validate the resulting artifact as a closed JSON Structure schema.
  4. Give that graph to Structurize for projection.
  5. Inspect the target artifacts and any target-native imports it emits.

If resolution cannot close the graph, fail with the source URI and unresolved pointer. Record redirects, cached representations, and cycle or depth failures. Those diagnostics explain why the authoritative graph could not be assembled. Passing a partly resolved document downstream merely trades a precise import error for a misleading converter failure.

JSON Structure remains the authority throughout. The flattened schema records what the source model means; Structurize projects that model into another system’s available shapes. Imports must be resolved before conversion; otherwise Structurize receives an incomplete type graph.