Parse, resolve, and dereference JSON Schema $ref pointers on the JVM and Node.js.
This library is the Kotlin Multiplatform evolution of json-schema-ref-parser-jvm. It provides:
RefParseras the primary API for Kotlin and Node.js integrationsJavaRefParseras the blocking JVM facade for Java and Kotlin/JVM callers- a JVM compatibility layer for existing
$RefParserand$Refsusers - JS exports for Node.js runtimes
This is the library to choose for new JVM and Node.js integrations.
- New users should start with
json-schema-ref-parser-kmp. - Existing
json-schema-ref-parser-jvmusers can migrate incrementally using the JVM compatibility layer. - The
$RefParserAPI remains available on the JVM for compatibility, but it is not the primary API going forward.
Current status:
- The Kotlin Multiplatform core is working and is intended to match the JVM implementation behavior.
- The main API has been reshaped around
RefParser. - Circular reference handling has been reworked in the new implementation and still benefits from broader real-world validation.
If you work with JSON Schema, OpenAPI, or AsyncAPI specifications, you know the pain: schemas spread across multiple files, $ref pointers everywhere, and yet another ad hoc parser seems inevitable.
This library handles all of that for you. It is a full JSON Reference and JSON Pointer implementation that crawls even complex schemas and returns a simple object tree with source locations and resolved ref tracking.
val doc = RefParser("path/to/openapi.yml")
.dereference()
.mergeAllOf()
.getParsedDocument()
val schema: Map<String, Any?> = doc.schemaFor blocking callers on the JVM, use the JavaRefParser facade built on top of RefParser.
import io.zenwave360.jsonrefparser.JavaRefParser;
import io.zenwave360.jsonrefparser.model.ParsedDocument;
import java.io.File;
import java.util.Map;
File file = new File("src/main/resources/openapi.yml");
ParsedDocument doc = JavaRefParser.from(file)
.dereference()
.mergeAllOf()
.getParsedDocument();
Map<String, Object> schema = (Map<String, Object>) doc.getSchema();import { dereferenceSchema } from "@zenwave360/json-schema-ref-parser-kmp";
const doc = await dereferenceSchema("file:///workspace/openapi.yml", true);
console.log(doc.schema);Gradle:
dependencies {
implementation("io.zenwave360.jsonrefparser:json-schema-ref-parser-kmp-jvm:<version>")
}Maven:
<dependency>
<groupId>io.zenwave360.jsonrefparser</groupId>
<artifactId>json-schema-ref-parser-kmp-jvm</artifactId>
<version>${json-schema-ref-parser-kmp-jvm.version}</version>
</dependency>dependencies {
implementation("io.zenwave360.jsonrefparser:json-schema-ref-parser-kmp:<version>")
}The Node.js API is available from the JS target exports:
parseSchemaText(input, baseUri?)dereferenceSchema(uri, mergeAllOf?)dereferenceSchemaText(input, baseUri?, mergeAllOf?)
The npm package name is @zenwave360/json-schema-ref-parser-kmp. npm publishing is not enabled yet, so this repository currently publishes Maven Central artifacts only.
RefParser is the main suspend-first API for Kotlin and Node.js integrations. Blocking callers on the JVM should use JavaRefParser.
Resolves all $ref pointers and replaces them inline, including references to external files and remote URLs.
val doc = RefParser("path/to/schema.yml")
.dereference()
.getParsedDocument()After dereferencing, merges every allOf array into its parent object, combining fields such as properties and required.
val doc = RefParser("path/to/schema.yml")
.dereference()
.mergeAllOf()
.getParsedDocument()By default, circular references are resolved by preserving object identity. You can also skip them or fail fast.
val doc = RefParser(
uri = "path/to/schema.yml",
options = RefParserOptions(onCircular = OnCircular.SKIP),
).dereference().getParsedDocument()
println(doc.hasCircularRefs) // trueval doc = RefParser(
uri = "path/to/schema.yml",
options = RefParserOptions(onMissing = OnMissing.SKIP),
).dereference().getParsedDocument()val doc = RefParser(
uri = "path/to/schema.yml",
auth = listOf(
AuthenticationValue(
key = "Authorization",
value = "Bearer <token>",
urlMatcher = { url -> url.contains("api.example.com") },
)
),
).dereference().getParsedDocument()Replace the loader chain completely:
val doc = RefParser("classpath:/schemas/openapi.yml")
.withLoaders(
ClasspathLoader(pluginClassLoader),
FileLoader(),
HttpLoader(),
)
.dereference()
.getParsedDocument()Patch only the default chain, replacing matching loader types and preserving the rest:
val doc = RefParser("classpath:/schemas/openapi.yml")
.withDefaultLoaders(
ClasspathLoader(pluginClassLoader),
)
.dereference()
.getParsedDocument()val doc = RefParser("classpath:/schemas/openapi.yml")
.dereference()
.getParsedDocument()Every node in the parsed document carries its original file and line and column range, even after dereferencing across multiple files.
val doc = RefParser("path/to/schema.yml")
.dereference()
.getParsedDocument()
val location = doc.locations["/info"]
println("${location?.file}:${location?.line}:${location?.column}")After dereferencing, you can look up which $ref string a given object came from.
val doc = RefParser("path/to/schema.yml")
.dereference()
.getParsedDocument()
val ref = doc.getOriginalRef(someSchemaObject)
println(ref?.refString) // e.g. "#/components/schemas/Pet"Useful in tests or when you already have the document text in memory.
val yaml = """
type: object
properties:
name:
type: string
""".trimIndent()
val doc = RefParser.fromText(yaml).dereference().getParsedDocument()Blocking JVM callers should use the JVM facade:
import io.zenwave360.jsonrefparser.JavaRefParser;
import io.zenwave360.jsonrefparser.model.OnCircular;
import io.zenwave360.jsonrefparser.model.OnMissing;
import io.zenwave360.jsonrefparser.model.ParsedDocument;
import io.zenwave360.jsonrefparser.model.RefParserOptions;
import java.io.File;
File file = new File("src/main/resources/openapi.yml");
ParsedDocument doc = JavaRefParser.from(file)
.withOptions(new RefParserOptions(OnCircular.SKIP, OnMissing.FAIL))
.dereference()
.mergeAllOf()
.getParsedDocument();
System.out.println(doc.getSchema());Patch only the default classpath loader while keeping the default file and HTTP loaders:
import io.zenwave360.jsonrefparser.JavaRefParser;
import java.net.URI;
var doc = JavaRefParser.from(URI.create("classpath:catalog/service/asyncapi.yml"))
.withResourceClassLoader(pluginClassLoader)
.dereference()
.getParsedDocument();Patch the default loader chain explicitly:
import io.zenwave360.jsonrefparser.JavaRefParser;
import io.zenwave360.jsonrefparser.io.ClasspathLoader;
import java.util.Arrays;
var doc = JavaRefParser.from("classpath:catalog/service/asyncapi.yml")
.withDefaultLoaders(Arrays.asList(
new ClasspathLoader(pluginClassLoader)
))
.dereference()
.getParsedDocument();Replace the loader chain completely:
import io.zenwave360.jsonrefparser.JavaRefParser;
import io.zenwave360.jsonrefparser.io.ClasspathLoader;
import io.zenwave360.jsonrefparser.io.FileLoader;
import io.zenwave360.jsonrefparser.io.HttpLoader;
import java.util.Arrays;
var doc = JavaRefParser.from("classpath:catalog/service/asyncapi.yml")
.withLoaders(Arrays.asList(
new ClasspathLoader(pluginClassLoader),
new FileLoader(),
new HttpLoader()
))
.dereference()
.getParsedDocument();The JVM module still ships the legacy compatibility API for existing json-schema-ref-parser-jvm users:
$RefParser$Refs$Ref$RefParserOptions
Use this when you want a low-friction migration path from the old JVM library. For new JVM code, prefer JavaRefParser.
import static io.zenwave360.jsonrefparser.$RefParserOptions.OnCircular.SKIP;
import io.zenwave360.jsonrefparser.$RefParser;
import io.zenwave360.jsonrefparser.$RefParserOptions;
import io.zenwave360.jsonrefparser.$Refs;
import java.io.File;
File file = new File("src/main/resources/openapi.yml");
$Refs refs = new $RefParser(file)
.withOptions(new $RefParserOptions().withOnCircular(SKIP))
.dereference()
.mergeAllOf()
.getRefs();
Object schema = refs.schema();import io.zenwave360.jsonrefparser.$RefParser;
import java.io.File;
File file = new File("src/main/resources/openapi.yml");
var range = new $RefParser(file)
.parse()
.getRefs()
.getJsonLocationRange("$.info");The JS target exports plain object APIs designed for ESM runtimes in Node.js.
Dereference a file:
import { dereferenceSchema } from "@zenwave360/json-schema-ref-parser-kmp";
const doc = await dereferenceSchema("file:///workspace/openapi.yml", true);
console.log(doc.hasCircularRefs);
console.log(doc.locations["/info"]);Dereference in-memory text:
import { dereferenceSchemaText } from "@zenwave360/json-schema-ref-parser-kmp";
const yaml = `
type: object
properties:
pet:
$ref: "#/definitions/Pet"
definitions:
Pet:
type: object
properties:
name:
type: string
`;
const doc = await dereferenceSchemaText(yaml, "memory://pet.yml", true);
console.log(doc.schema);Parse without dereferencing:
import { parseSchemaText } from "@zenwave360/json-schema-ref-parser-kmp";
const doc = parseSchemaText("{\"type\":\"object\"}", "memory://schema.json");
console.log(doc.locations[""]);- Parses JSON, YAML, and Avro schemas, or any mix of them
- Dereferences
$refpointers into a plain object tree - Cross-file references: local files, remote URLs, and classpath resources on the JVM
- Object identity: two
$refpointers to the same target resolve to the same object instance - Source locations for every parsed node
- Original ref tracking for resolved objects
- Merges
allOfarrays into a single object - Authentication headers and query parameters for remote loading
- Circular reference detection with resolve, skip, and fail modes
- Missing reference handling with skip and fail modes
- JVM and Node.js support through Kotlin Multiplatform
- JVM compatibility layer for existing
json-schema-ref-parser-jvmusers
Maven Central publishing is handled by GitHub Actions. Repository prerequisites and the release sequence are documented in RELEASING.md.
JSON Schema $Ref Parser KMP is free and open-source under the MIT license.