@apidevtools/json-schema-ref-parser 14.2.1 → 15.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +0 -4
  2. package/dist/lib/bundle.d.ts +3 -3
  3. package/dist/lib/bundle.js +16 -54
  4. package/dist/lib/dereference.d.ts +1 -2
  5. package/dist/lib/dereference.js +12 -50
  6. package/dist/lib/index.js +42 -95
  7. package/dist/lib/normalize-args.d.ts +1 -1
  8. package/dist/lib/normalize-args.js +4 -7
  9. package/dist/lib/options.d.ts +6 -0
  10. package/dist/lib/options.js +16 -23
  11. package/dist/lib/parse.js +11 -46
  12. package/dist/lib/parsers/binary.js +1 -3
  13. package/dist/lib/parsers/json.js +4 -6
  14. package/dist/lib/parsers/text.js +3 -5
  15. package/dist/lib/parsers/yaml.js +7 -12
  16. package/dist/lib/pointer.d.ts +3 -2
  17. package/dist/lib/pointer.js +18 -63
  18. package/dist/lib/ref.d.ts +5 -3
  19. package/dist/lib/ref.js +60 -52
  20. package/dist/lib/refs.d.ts +1 -1
  21. package/dist/lib/refs.js +7 -46
  22. package/dist/lib/resolve-external.js +10 -48
  23. package/dist/lib/resolvers/file.js +7 -45
  24. package/dist/lib/resolvers/http.js +5 -40
  25. package/dist/lib/types/index.d.ts +1 -1
  26. package/dist/lib/types/index.js +1 -2
  27. package/dist/lib/util/convert-path-to-posix.js +3 -9
  28. package/dist/lib/util/errors.d.ts +1 -1
  29. package/dist/lib/util/errors.js +17 -33
  30. package/dist/lib/util/is-windows.js +1 -5
  31. package/dist/lib/util/maybe.js +4 -10
  32. package/dist/lib/util/next.js +1 -3
  33. package/dist/lib/util/plugins.js +4 -10
  34. package/dist/lib/util/url.d.ts +1 -1
  35. package/dist/lib/util/url.js +40 -88
  36. package/lib/bundle.ts +12 -9
  37. package/lib/dereference.ts +3 -4
  38. package/lib/normalize-args.ts +1 -1
  39. package/lib/options.ts +8 -0
  40. package/lib/pointer.ts +19 -20
  41. package/lib/ref.ts +55 -5
  42. package/lib/refs.ts +2 -2
  43. package/lib/types/index.ts +1 -1
  44. package/lib/util/errors.ts +2 -2
  45. package/lib/util/url.ts +13 -7
  46. package/package.json +21 -20
@@ -1,64 +1,11 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- var __importDefault = (this && this.__importDefault) || function (mod) {
36
- return (mod && mod.__esModule) ? mod : { "default": mod };
37
- };
38
- Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.parse = void 0;
40
- exports.resolve = resolve;
41
- exports.cwd = cwd;
42
- exports.getProtocol = getProtocol;
43
- exports.getExtension = getExtension;
44
- exports.stripQuery = stripQuery;
45
- exports.getHash = getHash;
46
- exports.stripHash = stripHash;
47
- exports.isHttp = isHttp;
48
- exports.isUnsafeUrl = isUnsafeUrl;
49
- exports.isFileSystemPath = isFileSystemPath;
50
- exports.fromFileSystemPath = fromFileSystemPath;
51
- exports.toFileSystemPath = toFileSystemPath;
52
- exports.safePointerToPath = safePointerToPath;
53
- exports.relative = relative;
54
- const convert_path_to_posix_1 = __importDefault(require("./convert-path-to-posix"));
55
- const path_1 = __importStar(require("path"));
1
+ import convertPathToPosix from "./convert-path-to-posix.js";
2
+ import path, { win32 } from "path";
56
3
  const forwardSlashPattern = /\//g;
57
4
  const protocolPattern = /^(\w{2,}):\/\//i;
58
5
  const jsonPointerSlash = /~1/g;
59
6
  const jsonPointerTilde = /~0/g;
60
- const path_2 = require("path");
61
- const is_windows_1 = require("./is-windows");
7
+ import { join } from "path";
8
+ import { isWindows } from "./is-windows.js";
62
9
  // RegExp patterns to URL-encode special characters in local filesystem paths
63
10
  const urlEncodePatterns = [
64
11
  [/\?/g, "%3F"],
@@ -66,31 +13,36 @@ const urlEncodePatterns = [
66
13
  ];
67
14
  // RegExp patterns to URL-decode special characters for local filesystem paths
68
15
  const urlDecodePatterns = [/%23/g, "#", /%24/g, "$", /%26/g, "&", /%2C/g, ",", /%40/g, "@"];
69
- const parse = (u) => new URL(u);
70
- exports.parse = parse;
16
+ export const parse = (u) => new URL(u);
71
17
  /**
72
18
  * Returns resolved target URL relative to a base URL in a manner similar to that of a Web browser resolving an anchor tag HREF.
73
19
  *
74
20
  * @returns
75
21
  */
76
- function resolve(from, to) {
22
+ export function resolve(from, to) {
77
23
  // we use a non-existent URL to check if its a relative URL
78
- const fromUrl = new URL((0, convert_path_to_posix_1.default)(from), "https://aaa.nonexistanturl.com");
79
- const resolvedUrl = new URL((0, convert_path_to_posix_1.default)(to), fromUrl);
24
+ const fromUrl = new URL(convertPathToPosix(from), "https://aaa.nonexistanturl.com");
25
+ const resolvedUrl = new URL(convertPathToPosix(to), fromUrl);
80
26
  const endSpaces = to.match(/(\s*)$/)?.[1] || "";
81
27
  if (resolvedUrl.hostname === "aaa.nonexistanturl.com") {
82
28
  // `from` is a relative URL.
83
29
  const { pathname, search, hash } = resolvedUrl;
84
- return pathname + search + hash + endSpaces;
30
+ return pathname + search + decodeURIComponent(hash) + endSpaces;
31
+ }
32
+ const resolved = resolvedUrl.toString() + endSpaces;
33
+ // if there is a #, we want to split on the first one only, and decode the part after
34
+ if (resolved.includes("#")) {
35
+ const [base, hash] = resolved.split("#", 2);
36
+ return base + "#" + decodeURIComponent(hash || "");
85
37
  }
86
- return resolvedUrl.toString() + endSpaces;
38
+ return resolved;
87
39
  }
88
40
  /**
89
41
  * Returns the current working directory (in Node) or the current page URL (in browsers).
90
42
  *
91
43
  * @returns
92
44
  */
93
- function cwd() {
45
+ export function cwd() {
94
46
  if (typeof window !== "undefined" && window.location && window.location.href) {
95
47
  const href = window.location.href;
96
48
  if (!href || !href.startsWith("http")) {
@@ -123,7 +75,7 @@ function cwd() {
123
75
  * @param path
124
76
  * @returns
125
77
  */
126
- function getProtocol(path) {
78
+ export function getProtocol(path) {
127
79
  const match = protocolPattern.exec(path || "");
128
80
  if (match) {
129
81
  return match[1].toLowerCase();
@@ -137,7 +89,7 @@ function getProtocol(path) {
137
89
  * @param path
138
90
  * @returns
139
91
  */
140
- function getExtension(path) {
92
+ export function getExtension(path) {
141
93
  const lastDot = path.lastIndexOf(".");
142
94
  if (lastDot >= 0) {
143
95
  return stripQuery(path.substring(lastDot).toLowerCase());
@@ -150,7 +102,7 @@ function getExtension(path) {
150
102
  * @param path
151
103
  * @returns
152
104
  */
153
- function stripQuery(path) {
105
+ export function stripQuery(path) {
154
106
  const queryIndex = path.indexOf("?");
155
107
  if (queryIndex >= 0) {
156
108
  path = path.substring(0, queryIndex);
@@ -164,7 +116,7 @@ function stripQuery(path) {
164
116
  * @param path
165
117
  * @returns
166
118
  */
167
- function getHash(path) {
119
+ export function getHash(path) {
168
120
  if (!path) {
169
121
  return "#";
170
122
  }
@@ -180,7 +132,7 @@ function getHash(path) {
180
132
  * @param path
181
133
  * @returns
182
134
  */
183
- function stripHash(path) {
135
+ export function stripHash(path) {
184
136
  if (!path) {
185
137
  return "";
186
138
  }
@@ -196,7 +148,7 @@ function stripHash(path) {
196
148
  * @param path
197
149
  * @returns
198
150
  */
199
- function isHttp(path) {
151
+ export function isHttp(path) {
200
152
  const protocol = getProtocol(path);
201
153
  if (protocol === "http" || protocol === "https") {
202
154
  return true;
@@ -216,7 +168,7 @@ function isHttp(path) {
216
168
  * @param path - The URL or path to check
217
169
  * @returns true if the URL is unsafe/internal, false otherwise
218
170
  */
219
- function isUnsafeUrl(path) {
171
+ export function isUnsafeUrl(path) {
220
172
  if (!path || typeof path !== "string") {
221
173
  return true;
222
174
  }
@@ -363,7 +315,7 @@ function isInternalPort(port) {
363
315
  * @param path
364
316
  * @returns
365
317
  */
366
- function isFileSystemPath(path) {
318
+ export function isFileSystemPath(path) {
367
319
  // @ts-ignore
368
320
  if (typeof window !== "undefined" || (typeof process !== "undefined" && process.browser)) {
369
321
  // We're running in a browser, so assume that all paths are URLs.
@@ -389,24 +341,24 @@ function isFileSystemPath(path) {
389
341
  * @param path
390
342
  * @returns
391
343
  */
392
- function fromFileSystemPath(path) {
344
+ export function fromFileSystemPath(path) {
393
345
  // Step 1: On Windows, replace backslashes with forward slashes,
394
346
  // rather than encoding them as "%5C"
395
- if ((0, is_windows_1.isWindows)()) {
347
+ if (isWindows()) {
396
348
  const projectDir = cwd();
397
349
  const upperPath = path.toUpperCase();
398
- const projectDirPosixPath = (0, convert_path_to_posix_1.default)(projectDir);
350
+ const projectDirPosixPath = convertPathToPosix(projectDir);
399
351
  const posixUpper = projectDirPosixPath.toUpperCase();
400
352
  const hasProjectDir = upperPath.includes(posixUpper);
401
353
  const hasProjectUri = upperPath.includes(posixUpper);
402
- const isAbsolutePath = path_1.win32?.isAbsolute(path) ||
354
+ const isAbsolutePath = win32?.isAbsolute(path) ||
403
355
  path.startsWith("http://") ||
404
356
  path.startsWith("https://") ||
405
357
  path.startsWith("file://");
406
358
  if (!(hasProjectDir || hasProjectUri || isAbsolutePath) && !projectDir.startsWith("http")) {
407
- path = (0, path_2.join)(projectDir, path);
359
+ path = join(projectDir, path);
408
360
  }
409
- path = (0, convert_path_to_posix_1.default)(path);
361
+ path = convertPathToPosix(path);
410
362
  }
411
363
  // Step 2: `encodeURI` will take care of MOST characters
412
364
  path = encodeURI(path);
@@ -421,7 +373,7 @@ function fromFileSystemPath(path) {
421
373
  /**
422
374
  * Converts a URL to a local filesystem path.
423
375
  */
424
- function toFileSystemPath(path, keepFileProtocol) {
376
+ export function toFileSystemPath(path, keepFileProtocol) {
425
377
  // Step 1: `decodeURI` will decode characters such as Cyrillic characters, spaces, etc.
426
378
  path = decodeURI(path);
427
379
  // Step 2: Manually decode characters that are not decoded by `decodeURI`.
@@ -437,7 +389,7 @@ function toFileSystemPath(path, keepFileProtocol) {
437
389
  // Strip-off the protocol, and the initial "/", if there is one
438
390
  path = path.replace(/^file:\/\//, "").replace(/^\//, "");
439
391
  // insert a colon (":") after the drive letter on Windows
440
- if ((0, is_windows_1.isWindows)() && path[1] === "/") {
392
+ if (isWindows() && path[1] === "/") {
441
393
  path = `${path[0]}:${path.substring(1)}`;
442
394
  }
443
395
  if (keepFileProtocol) {
@@ -449,11 +401,11 @@ function toFileSystemPath(path, keepFileProtocol) {
449
401
  // On Windows, it will start with something like "C:/".
450
402
  // On Posix, it will start with "/"
451
403
  isFileUrl = false;
452
- path = (0, is_windows_1.isWindows)() ? path : "/" + path;
404
+ path = isWindows() ? path : "/" + path;
453
405
  }
454
406
  }
455
407
  // Step 4: Normalize Windows paths (unless it's a "file://" URL)
456
- if ((0, is_windows_1.isWindows)() && !isFileUrl) {
408
+ if (isWindows() && !isFileUrl) {
457
409
  // Replace forward slashes with backslashes
458
410
  path = path.replace(forwardSlashPattern, "\\");
459
411
  // Capitalize the drive letter
@@ -469,7 +421,7 @@ function toFileSystemPath(path, keepFileProtocol) {
469
421
  * @param pointer
470
422
  * @returns
471
423
  */
472
- function safePointerToPath(pointer) {
424
+ export function safePointerToPath(pointer) {
473
425
  if (pointer.length <= 1 || pointer[0] !== "#" || pointer[1] !== "/") {
474
426
  return [];
475
427
  }
@@ -477,15 +429,15 @@ function safePointerToPath(pointer) {
477
429
  .slice(2)
478
430
  .split("/")
479
431
  .map((value) => {
480
- return decodeURIComponent(value).replace(jsonPointerSlash, "/").replace(jsonPointerTilde, "~");
432
+ return value.replace(jsonPointerSlash, "/").replace(jsonPointerTilde, "~");
481
433
  });
482
434
  }
483
- function relative(from, to) {
435
+ export function relative(from, to) {
484
436
  if (!isFileSystemPath(from) || !isFileSystemPath(to)) {
485
437
  return resolve(from, to);
486
438
  }
487
- const fromDir = path_1.default.dirname(stripHash(from));
439
+ const fromDir = path.dirname(stripHash(from));
488
440
  const toPath = stripHash(to);
489
- const result = path_1.default.relative(fromDir, toPath);
441
+ const result = path.relative(fromDir, toPath);
490
442
  return result + getHash(to);
491
443
  }
package/lib/bundle.ts CHANGED
@@ -2,10 +2,10 @@ import $Ref from "./ref.js";
2
2
  import Pointer from "./pointer.js";
3
3
  import * as url from "./util/url.js";
4
4
  import type $Refs from "./refs.js";
5
- import type $RefParser from "./index";
6
- import type { ParserOptions } from "./index";
7
- import type { JSONSchema } from "./index";
8
- import type { BundleOptions } from "./options";
5
+ import type $RefParser from "./index.js";
6
+ import type { ParserOptions } from "./index.js";
7
+ import type { JSONSchema } from "./index.js";
8
+ import type { BundleOptions } from "./options.js";
9
9
 
10
10
  export interface InventoryEntry {
11
11
  $ref: any;
@@ -40,7 +40,7 @@ function bundle<S extends object = JSONSchema, O extends ParserOptions<S> = Pars
40
40
  crawl<S, O>(parser, "schema", parser.$refs._root$Ref.path + "#", "#", 0, inventory, parser.$refs, options);
41
41
 
42
42
  // Remap all $ref pointers
43
- remap(inventory);
43
+ remap<S, O>(inventory, options);
44
44
  }
45
45
 
46
46
  /**
@@ -203,7 +203,10 @@ function inventory$Ref<S extends object = JSONSchema, O extends ParserOptions<S>
203
203
  *
204
204
  * @param inventory
205
205
  */
206
- function remap(inventory: InventoryEntry[]) {
206
+ function remap<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOptions<S>>(
207
+ inventory: InventoryEntry[],
208
+ options: O,
209
+ ) {
207
210
  // Group & sort all the $ref pointers, so they're in the order that we need to dereference/remap them
208
211
  inventory.sort((a: InventoryEntry, b: InventoryEntry) => {
209
212
  if (a.file !== b.file) {
@@ -254,10 +257,10 @@ function remap(inventory: InventoryEntry[]) {
254
257
  // This $ref already resolves to the main JSON Schema file
255
258
  entry.$ref.$ref = entry.hash;
256
259
  } else if (entry.file === file && entry.hash === hash) {
257
- // This $ref points to the same value as the prevous $ref, so remap it to the same path
260
+ // This $ref points to the same value as the previous $ref, so remap it to the same path
258
261
  entry.$ref.$ref = pathFromRoot;
259
262
  } else if (entry.file === file && entry.hash.indexOf(hash + "/") === 0) {
260
- // This $ref points to a sub-value of the prevous $ref, so remap it beneath that path
263
+ // This $ref points to a sub-value of the previous $ref, so remap it beneath that path
261
264
  entry.$ref.$ref = Pointer.join(pathFromRoot, Pointer.parse(entry.hash.replace(hash, "#")));
262
265
  } else {
263
266
  // We've moved to a new file or new hash
@@ -267,7 +270,7 @@ function remap(inventory: InventoryEntry[]) {
267
270
 
268
271
  // This is the first $ref to point to this value, so dereference the value.
269
272
  // Any other $refs that point to the same value will point to this $ref instead
270
- entry.$ref = entry.parent[entry.key] = $Ref.dereference(entry.$ref, entry.value);
273
+ entry.$ref = entry.parent[entry.key] = $Ref.dereference(entry.$ref, entry.value, options);
271
274
 
272
275
  if (entry.circular) {
273
276
  // This $ref points to itself
@@ -3,9 +3,8 @@ import Pointer from "./pointer.js";
3
3
  import * as url from "./util/url.js";
4
4
  import type $Refs from "./refs.js";
5
5
  import type { DereferenceOptions, ParserOptions } from "./options.js";
6
- import type { JSONSchema } from "./types";
7
- import type $RefParser from "./index";
8
- import { TimeoutError } from "./util/errors";
6
+ import { type $RefParser, type JSONSchema } from "./index.js";
7
+ import { TimeoutError } from "./util/errors.js";
9
8
 
10
9
  export default dereference;
11
10
 
@@ -278,7 +277,7 @@ function dereference$Ref<S extends object = JSONSchema, O extends ParserOptions<
278
277
  }
279
278
 
280
279
  // Dereference the JSON reference
281
- let dereferencedValue = $Ref.dereference($ref, pointer.value);
280
+ let dereferencedValue = $Ref.dereference($ref, pointer.value, options);
282
281
 
283
282
  // Crawl the dereferenced value (unless it's circular)
284
283
  if (!circular) {
@@ -1,6 +1,6 @@
1
1
  import type { Options, ParserOptions } from "./options.js";
2
2
  import { getNewOptions } from "./options.js";
3
- import type { JSONSchema, SchemaCallback } from "./types";
3
+ import type { JSONSchema, SchemaCallback } from "./index.js";
4
4
 
5
5
  // I really dislike this function and the way it's written. It's not clear what it's doing, and it's way too flexible
6
6
  // In the future, I'd like to deprecate the api and accept only named parameters in index.ts
package/lib/options.ts CHANGED
@@ -82,6 +82,13 @@ export interface DereferenceOptions {
82
82
  * Default: `relative`
83
83
  */
84
84
  externalReferenceResolution?: "relative" | "root";
85
+
86
+ /**
87
+ * Whether duplicate keys should be merged when dereferencing objects.
88
+ *
89
+ * Default: `true`
90
+ */
91
+ mergeKeys?: boolean;
85
92
  }
86
93
 
87
94
  /**
@@ -229,6 +236,7 @@ export const getJsonSchemaRefParserDefaultOptions = () => {
229
236
  */
230
237
  excludedPathMatcher: () => false,
231
238
  referenceResolution: "relative",
239
+ mergeKeys: true,
232
240
  },
233
241
 
234
242
  mutateInputSchema: true,
package/lib/pointer.ts CHANGED
@@ -3,7 +3,8 @@ import type { ParserOptions } from "./options.js";
3
3
  import $Ref from "./ref.js";
4
4
  import * as url from "./util/url.js";
5
5
  import { JSONParserError, InvalidPointerError, MissingPointerError, isHandledError } from "./util/errors.js";
6
- import type { JSONSchema } from "./types";
6
+ import type { JSONSchema } from "./index.js";
7
+ import type { JSONSchema4Type, JSONSchema6Type, JSONSchema7Type } from "json-schema";
7
8
 
8
9
  export const nullSymbol = Symbol("null");
9
10
 
@@ -12,14 +13,6 @@ const tildes = /~/g;
12
13
  const escapedSlash = /~1/g;
13
14
  const escapedTilde = /~0/g;
14
15
 
15
- const safeDecodeURIComponent = (encodedURIComponent: string): string => {
16
- try {
17
- return decodeURIComponent(encodedURIComponent);
18
- } catch {
19
- return encodedURIComponent;
20
- }
21
- };
22
-
23
16
  /**
24
17
  * This class represents a single JSON pointer and its resolved value.
25
18
  *
@@ -90,7 +83,7 @@ class Pointer<S extends object = JSONSchema, O extends ParserOptions<S> = Parser
90
83
  */
91
84
  resolve(obj: S, options?: O, pathFromRoot?: string) {
92
85
  const tokens = Pointer.parse(this.path, this.originalPath);
93
- const found: any = [];
86
+ const found: string[] = [];
94
87
 
95
88
  // Crawl the object, one token at a time
96
89
  this.value = unwrapOrThrow(obj);
@@ -123,7 +116,7 @@ class Pointer<S extends object = JSONSchema, O extends ParserOptions<S> = Parser
123
116
  // actually instead pointing to an existing `null` value then we should use that
124
117
  // `null` value.
125
118
  if (token in this.value && this.value[token] === null) {
126
- // We use a `null` symbol for internal tracking to differntiate between a general `null`
119
+ // We use a `null` symbol for internal tracking to differentiate between a general `null`
127
120
  // value and our expected `null` value.
128
121
  this.value = nullSymbol;
129
122
  continue;
@@ -163,7 +156,7 @@ class Pointer<S extends object = JSONSchema, O extends ParserOptions<S> = Parser
163
156
  * @returns
164
157
  * Returns the modified object, or an entirely new object if the entire object is overwritten.
165
158
  */
166
- set(obj: S, value: any, options?: O) {
159
+ set(obj: S, value: JSONSchema4Type | JSONSchema6Type | JSONSchema7Type, options?: O) {
167
160
  const tokens = Pointer.parse(this.path);
168
161
  let token;
169
162
 
@@ -190,7 +183,7 @@ class Pointer<S extends object = JSONSchema, O extends ParserOptions<S> = Parser
190
183
  }
191
184
 
192
185
  // Set the value of the final token
193
- resolveIf$Ref(this, options);
186
+ resolveIf$Ref<S, O>(this, options);
194
187
  token = tokens[tokens.length - 1];
195
188
  setValue(this, token, value);
196
189
 
@@ -225,7 +218,7 @@ class Pointer<S extends object = JSONSchema, O extends ParserOptions<S> = Parser
225
218
 
226
219
  // Decode each part, according to RFC 6901
227
220
  for (let i = 0; i < split.length; i++) {
228
- split[i] = safeDecodeURIComponent(split[i].replace(escapedSlash, "/").replace(escapedTilde, "~"));
221
+ split[i] = split[i].replace(escapedSlash, "/").replace(escapedTilde, "~");
229
222
  }
230
223
 
231
224
  if (split[0] !== "") {
@@ -253,7 +246,9 @@ class Pointer<S extends object = JSONSchema, O extends ParserOptions<S> = Parser
253
246
  for (let i = 0; i < tokens.length; i++) {
254
247
  const token = tokens[i];
255
248
  // Encode the token, according to RFC 6901
256
- base += "/" + encodeURIComponent(token.replace(tildes, "~0").replace(slashes, "~1"));
249
+ // RFC 6901 only requires encoding ~ as ~0 and / as ~1
250
+ // We do NOT use encodeURIComponent as it encodes characters like $ which should remain literal
251
+ base += "/" + token.replace(tildes, "~0").replace(slashes, "~1");
257
252
  }
258
253
 
259
254
  return base;
@@ -271,7 +266,11 @@ class Pointer<S extends object = JSONSchema, O extends ParserOptions<S> = Parser
271
266
  * @param [pathFromRoot] - the path of place that initiated resolving
272
267
  * @returns - Returns `true` if the resolution path changed
273
268
  */
274
- function resolveIf$Ref(pointer: any, options: any, pathFromRoot?: any) {
269
+ function resolveIf$Ref<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOptions<S>>(
270
+ pointer: Pointer,
271
+ options: O | undefined,
272
+ pathFromRoot?: string,
273
+ ) {
275
274
  // Is the value a JSON reference? (and allowed?)
276
275
 
277
276
  if ($Ref.isAllowed$Ref(pointer.value, options)) {
@@ -291,7 +290,7 @@ function resolveIf$Ref(pointer: any, options: any, pathFromRoot?: any) {
291
290
  if ($Ref.isExtended$Ref(pointer.value)) {
292
291
  // This JSON reference "extends" the resolved value, rather than simply pointing to it.
293
292
  // So the resolved path does NOT change. Just the value does.
294
- pointer.value = $Ref.dereference(pointer.value, resolved.value);
293
+ pointer.value = $Ref.dereference(pointer.value, resolved.value, options);
295
294
  return false;
296
295
  } else {
297
296
  // Resolve the reference
@@ -318,7 +317,7 @@ export default Pointer;
318
317
  * @param value - The value to assign
319
318
  * @returns - Returns the assigned value
320
319
  */
321
- function setValue(pointer: any, token: any, value: any) {
320
+ function setValue(pointer: Pointer, token: string, value: JSONSchema4Type | JSONSchema6Type | JSONSchema7Type) {
322
321
  if (pointer.value && typeof pointer.value === "object") {
323
322
  if (token === "-" && Array.isArray(pointer.value)) {
324
323
  pointer.value.push(value);
@@ -333,7 +332,7 @@ function setValue(pointer: any, token: any, value: any) {
333
332
  return value;
334
333
  }
335
334
 
336
- function unwrapOrThrow(value: any) {
335
+ function unwrapOrThrow(value: unknown) {
337
336
  if (isHandledError(value)) {
338
337
  throw value;
339
338
  }
@@ -341,6 +340,6 @@ function unwrapOrThrow(value: any) {
341
340
  return value;
342
341
  }
343
342
 
344
- function isRootPath(pathFromRoot: any): boolean {
343
+ function isRootPath(pathFromRoot: string | unknown): boolean {
345
344
  return typeof pathFromRoot == "string" && Pointer.parse(pathFromRoot).length == 0;
346
345
  }
package/lib/ref.ts CHANGED
@@ -4,7 +4,8 @@ import { InvalidPointerError, isHandledError, normalizeError } from "./util/erro
4
4
  import { safePointerToPath, stripHash, getHash } from "./util/url.js";
5
5
  import type $Refs from "./refs.js";
6
6
  import type { ParserOptions } from "./options.js";
7
- import type { JSONSchema } from "./types";
7
+ import type { JSONSchema } from "./index.js";
8
+ import type { JSONSchema4Type, JSONSchema6Type, JSONSchema7Type } from "json-schema";
8
9
 
9
10
  export type $RefError = JSONParserError | ResolverError | ParserError | MissingPointerError;
10
11
 
@@ -150,7 +151,7 @@ class $Ref<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOpt
150
151
  * @param path - The full path of the property to set, optionally with a JSON pointer in the hash
151
152
  * @param value - The value to assign
152
153
  */
153
- set(path: string, value: any) {
154
+ set(path: string, value: JSONSchema4Type | JSONSchema6Type | JSONSchema7Type) {
154
155
  const pointer = new Pointer(this, path);
155
156
  this.value = pointer.set(this.value, value);
156
157
  if (this.value === nullSymbol) {
@@ -273,14 +274,16 @@ class $Ref<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOpt
273
274
  *
274
275
  * @param $ref - The JSON reference object (the one with the "$ref" property)
275
276
  * @param resolvedValue - The resolved value, which can be any type
277
+ * @param options - The options
276
278
  * @returns - Returns the dereferenced value
277
279
  */
278
280
  static dereference<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOptions<S>>(
279
281
  $ref: $Ref<S, O>,
280
282
  resolvedValue: S,
283
+ options?: O,
281
284
  ): S {
282
285
  if (resolvedValue && typeof resolvedValue === "object" && $Ref.isExtended$Ref($ref)) {
283
- const merged = {};
286
+ const merged = {} as Partial<S>;
284
287
  for (const key of Object.keys($ref)) {
285
288
  if (key !== "$ref") {
286
289
  // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
@@ -288,10 +291,24 @@ class $Ref<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOpt
288
291
  }
289
292
  }
290
293
 
291
- for (const key of Object.keys(resolvedValue)) {
294
+ const mergeKeys = options?.dereference?.mergeKeys ?? true;
295
+
296
+ for (const _key of Object.keys(resolvedValue)) {
297
+ const key = _key as keyof S;
292
298
  if (!(key in merged)) {
293
- // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
294
299
  merged[key] = resolvedValue[key];
300
+ } else {
301
+ // TODO: this behavior should be configurable from options on the CLI
302
+ // Key is already in merged, so we should merge them if both are objects
303
+ if (
304
+ mergeKeys &&
305
+ typeof merged[key] === "object" &&
306
+ merged[key] !== null &&
307
+ typeof resolvedValue[key] === "object" &&
308
+ resolvedValue[key] !== null
309
+ ) {
310
+ merged[key] = deepMerge<(typeof merged)[keyof S]>(resolvedValue[key], merged[key]);
311
+ }
295
312
  }
296
313
  }
297
314
 
@@ -303,4 +320,37 @@ class $Ref<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOpt
303
320
  }
304
321
  }
305
322
 
323
+ function deepMerge<T>(target: Partial<T>, source: Partial<T>): T {
324
+ //return {...target, ...source};
325
+
326
+ // If either isn't an object, just return source (overwrite)
327
+ if (typeof target !== "object" || target === null) {
328
+ return source as T;
329
+ }
330
+ if (typeof source !== "object" || source === null) {
331
+ return source;
332
+ }
333
+
334
+ // Ensure we don't mutate target directly
335
+ const output = Array.isArray(target) ? [...target] : { ...target };
336
+
337
+ for (const key of Object.keys(source)) {
338
+ // @ts-expect-error
339
+ if (Array.isArray(source[key])) {
340
+ // If it's an array, replace entirely (you can customize this to concat instead)
341
+ // @ts-expect-error
342
+ output[key] = [...source[key]];
343
+ // @ts-expect-error
344
+ } else if (typeof source[key] === "object" && source[key] !== null) {
345
+ // @ts-expect-error
346
+ output[key] = deepMerge(target[key], source[key]);
347
+ } else {
348
+ // @ts-expect-error
349
+ output[key] = source[key];
350
+ }
351
+ }
352
+
353
+ return output as T;
354
+ }
355
+
306
356
  export default $Ref;
package/lib/refs.ts CHANGED
@@ -2,8 +2,8 @@ import $Ref from "./ref.js";
2
2
  import * as url from "./util/url.js";
3
3
  import type { JSONSchema4Type, JSONSchema6Type, JSONSchema7Type } from "json-schema";
4
4
  import type { ParserOptions } from "./options.js";
5
- import convertPathToPosix from "./util/convert-path-to-posix";
6
- import type { JSONSchema } from "./types";
5
+ import convertPathToPosix from "./util/convert-path-to-posix.js";
6
+ import type { JSONSchema } from "./index.js";
7
7
 
8
8
  interface $RefsMap<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOptions<S>> {
9
9
  [url: string]: $Ref<S, O>;
@@ -7,7 +7,7 @@ import type {
7
7
  JSONSchema7Object,
8
8
  } from "json-schema";
9
9
  import type $Refs from "../refs.js";
10
- import type { ParserOptions } from "../options";
10
+ import type { ParserOptions } from "../options.js";
11
11
 
12
12
  export type JSONSchema = JSONSchema4 | JSONSchema6 | JSONSchema7;
13
13
  export type JSONSchemaObject = JSONSchema4Object | JSONSchema6Object | JSONSchema7Object;
@@ -2,7 +2,7 @@ import { getHash, stripHash, toFileSystemPath } from "./url.js";
2
2
  import type $RefParser from "../index.js";
3
3
  import type { ParserOptions } from "../index.js";
4
4
  import type { JSONSchema } from "../index.js";
5
- import type $Ref from "../ref";
5
+ import type $Ref from "../ref.js";
6
6
 
7
7
  export type JSONParserErrorType =
8
8
  | "EUNKNOWN"
@@ -201,7 +201,7 @@ export class InvalidPointerError extends JSONParserError {
201
201
  }
202
202
  }
203
203
 
204
- export function isHandledError(err: any): err is JSONParserError {
204
+ export function isHandledError(err: unknown): err is JSONParserError {
205
205
  return err instanceof JSONParserError || err instanceof JSONParserErrorGroup;
206
206
  }
207
207