Skip to content

Commit a25fded

Browse files
authored
Replace $ref with $reference in json-decycle (#3049)
1 parent 373183a commit a25fded

File tree

6 files changed

+82
-24
lines changed

6 files changed

+82
-24
lines changed

.changeset/warm-ducks-check.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@gitbook/react-openapi': patch
3+
---
4+
5+
Replace $ref with $reference in json-decycle

bun.lock

-3
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,6 @@
241241
"@scalar/oas-utils": "^0.2.120",
242242
"clsx": "^2.1.1",
243243
"flatted": "^3.2.9",
244-
"json-decycle": "^4.0.0",
245244
"json-xml-parse": "^1.3.0",
246245
"react-aria": "^3.37.0",
247246
"react-aria-components": "^1.6.0",
@@ -2070,8 +2069,6 @@
20702069

20712070
"json-buffer": ["[email protected]", "", {}, "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ=="],
20722071

2073-
"json-decycle": ["[email protected]", "", {}, "sha512-3GFL/vWazCbMu1kw+NdIfAHh6Ugq5pxkKcSUnK1f/Fw1nDtt1i+BiBfRJs0iPEKscYAz4k4+osvgjY95hmuJXQ=="],
2074-
20752072
"json-parse-even-better-errors": ["[email protected]", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="],
20762073

20772074
"json-schema": ["[email protected]", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],

packages/react-openapi/package.json

-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
"@scalar/oas-utils": "^0.2.120",
1717
"clsx": "^2.1.1",
1818
"flatted": "^3.2.9",
19-
"json-decycle": "^4.0.0",
2019
"json-xml-parse": "^1.3.0",
2120
"react-aria-components": "^1.6.0",
2221
"react-aria": "^3.37.0",

packages/react-openapi/src/OpenAPISchema.tsx

+8-19
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import type { OpenAPIV3 } from '@gitbook/openapi-parser';
66
import { useId } from 'react';
77

88
import clsx from 'clsx';
9-
import { retrocycle } from 'json-decycle';
109
import { Markdown } from './Markdown';
1110
import { OpenAPIDisclosure } from './OpenAPIDisclosure';
1211
import { OpenAPISchemaName } from './OpenAPISchemaName';
12+
import { retrocycle } from './decycle';
1313
import type { OpenAPIClientContext } from './types';
1414
import { checkIsReference, resolveDescription, resolveFirstExample } from './utils';
1515

@@ -122,7 +122,7 @@ export function OpenAPISchemaPropertiesFromServer(props: {
122122
return (
123123
<OpenAPISchemaProperties
124124
id={props.id}
125-
properties={safeJSONParse(props.properties)}
125+
properties={JSON.parse(props.properties, retrocycle())}
126126
context={props.context}
127127
/>
128128
);
@@ -172,7 +172,12 @@ export function OpenAPIRootSchemaFromServer(props: {
172172
schema: string;
173173
context: OpenAPIClientContext;
174174
}) {
175-
return <OpenAPIRootSchema schema={safeJSONParse(props.schema)} context={props.context} />;
175+
return (
176+
<OpenAPIRootSchema
177+
schema={JSON.parse(props.schema, retrocycle())}
178+
context={props.context}
179+
/>
180+
);
176181
}
177182

178183
/**
@@ -460,19 +465,3 @@ function getDisclosureLabel(schema: OpenAPIV3.SchemaObject): string {
460465

461466
return schema.title || 'child attributes';
462467
}
463-
464-
/**
465-
* Safely parse a JSON string using retrocycle.
466-
* If parsing fails, it falls back to standard JSON.parse.
467-
*/
468-
function safeJSONParse(jsonString: string) {
469-
try {
470-
return JSON.parse(jsonString, retrocycle());
471-
} catch {
472-
try {
473-
return JSON.parse(jsonString);
474-
} catch (fallbackError) {
475-
throw new Error(`Failed to parse JSON string: ${fallbackError}`);
476-
}
477-
}
478-
}

packages/react-openapi/src/OpenAPISchemaServer.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
2-
import { decycle } from 'json-decycle';
32
import {
43
OpenAPIRootSchemaFromServer,
54
OpenAPISchemaPropertiesFromServer,
65
type OpenAPISchemaPropertyEntry,
76
} from './OpenAPISchema';
7+
import { decycle } from './decycle';
88
import type { OpenAPIClientContext } from './types';
99

1010
export function OpenAPISchemaProperties(props: {

packages/react-openapi/src/decycle.ts

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Forked from: https://github.com/YChebotaev/json-decycle/blob/master/src/index.ts
2+
// Replaced `$ref` with `$reference` to avoid conflicts with OpenAPI
3+
4+
const isObject = (value: any): value is object =>
5+
typeof value === 'object' &&
6+
value != null &&
7+
!(value instanceof Boolean) &&
8+
!(value instanceof Date) &&
9+
!(value instanceof Number) &&
10+
!(value instanceof RegExp) &&
11+
!(value instanceof String);
12+
13+
const toPointer = (parts: string[]) =>
14+
`#${parts.map((part) => String(part).replace(/~/g, '~0').replace(/\//g, '~1')).join('/')}`;
15+
16+
export const decycle = () => {
17+
const paths = new WeakMap();
18+
19+
return function replacer(this: any, key: string | symbol, value: any) {
20+
if (key !== '$reference' && isObject(value)) {
21+
const seen = paths.has(value);
22+
23+
if (seen) {
24+
return { $reference: toPointer(paths.get(value)) };
25+
}
26+
27+
paths.set(value, [...(paths.get(this) ?? []), key]);
28+
}
29+
30+
return value;
31+
};
32+
};
33+
34+
export function retrocycle() {
35+
const parents = new WeakMap();
36+
const keys = new WeakMap();
37+
const refs = new Set();
38+
39+
function dereference(this: { [k: string]: any }, ref: { $reference: string }) {
40+
const parts = ref.$reference.slice(1).split('/');
41+
let key: any;
42+
let value = this;
43+
44+
for (let i = 0; i < parts.length; i++) {
45+
key = parts[i]?.replace(/~1/g, '/').replace(/~0/g, '~');
46+
value = value[key];
47+
}
48+
49+
const parent = parents.get(ref);
50+
parent[keys.get(ref)] = value;
51+
}
52+
53+
return function reviver(this: object, key: string | symbol, value: any) {
54+
if (key === '$reference') {
55+
refs.add(this);
56+
} else if (isObject(value)) {
57+
const isRoot = key === '' && Object.keys(this).length === 1;
58+
if (isRoot) {
59+
refs.forEach(dereference as any, this);
60+
} else {
61+
parents.set(value, this);
62+
keys.set(value, key);
63+
}
64+
}
65+
66+
return value;
67+
};
68+
}

0 commit comments

Comments
 (0)