|
| 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