json-schema-ref-parser icon indicating copy to clipboard operation
json-schema-ref-parser copied to clipboard

Keep $ref some how when bundle

Open wenerme opened this issue 5 years ago • 12 comments

Because $ref offen mean type of something, when render views, need the ref type to select the component.

e.g.

Address.json

{
  "type": "object",
  "properties": {
    "a": {
      "type": "string"
    }
  }
}

User.json

{
  "type": "object",
  "properties": {
    "address": {
      "$ref":"Address.json"
    }
  }
}

If the bundled result is

{
  "type": "object",
  "properties": {
    "address": {
      "_ref": "Address.json",
      "type": "object",
      "properties": {
        "a": {
          "type": "string"
        }
      }
    }
  }
}

Then the frontend can use the "_ref": "Address.json" to match the component more precisely.

wenerme avatar May 10 '20 11:05 wenerme

There's already some existing discussion going on about this in Issue #135. I think your solution could be acceptable, but I'd like to get some other opinions too

JamesMessinger avatar May 25 '20 09:05 JamesMessinger

Indeed this looks good to me. Though perhaps persisting $ref instead of _ref would be preferred?

+1

maxtuzz avatar May 26 '20 02:05 maxtuzz

Persisting $ref would confuse tools which know what a $ref is and want to do something about it. It could cause some infinite loops, or just errors from tools which warn you they don't know how to deal with a $ref - which is why you're dereferencing in the first place.

@MikeRalphson has done a bit of this.

philsturgeon avatar May 26 '20 08:05 philsturgeon

I simply allowed the caller to specify a property name for it to be copied to if they wanted $ref preservation.

MikeRalphson avatar May 26 '20 10:05 MikeRalphson

I like @MikeRalphson's approach. We could add an option, like say dereference.$refRename that defaults to false.

Any falsy value = remove the $ref as it does currently.

Any string value = rename the $ref to the specified value

JamesMessinger avatar May 26 '20 16:05 JamesMessinger

That would definitely work in our case (from Issue #135) and wouldn't generate a breaking change.

ftheomunhoz avatar May 26 '20 23:05 ftheomunhoz

Author of json-schema-to-typescript here. We'd love this features, as it would unblock a handful of issues related to emitting more intelligent TypeScript types.

As specific example, consider the following JSON Schema:

{
  additionalProperties: false,
  definitions: {
    a: {type: 'string'},
    b: {type: 'number'}
  },
  properties: {
    c: {
      additionalItems: false,
      items: [{$ref: '#/definitions/a'}, {$ref: '#/definitions/b'}],
      type: 'array'
    }
  },
  title: 'X,
  type: 'object'
}

We'd like to emit the following TypeScript types for this schema:

type A = string
type B = number

type X = {
  c: [] | [A] | [A, B]
}

Today, we accomplish this by always emitting standalone type names (here, A and B) for tuple members. However, this isn't the right behavior; it would be nice to refine our heuristic, where we look for the presences of $refs as a sign that a type is meant to be reused, and therefore, named (we already use ids and titles for this today).

To do that, we'd love a way to know what a schema's original $ref pointed to. Any key name is fine for us, though you might consider using a Symbol in order to avoid a backwards-incompatible change to this library, and to avoid colliding with existing keys.

As a side note: It's possible to infer something like this today (roughly: traverse over a dereferenced schema and its source schema at the same time, and annotate the dereferenced schema where you see a $ref in its source schema). But this is a hack that doesn't work when a source node is in another file, since $RefParser.resolve(..).get(..) returns a dereferenced schema, not a source schema.

Please let me known if this is not something you're interested in, and I'll go ahead and fork json-schema-ref-parser so we can patch it in for our use case.

bcherny avatar Jul 23 '20 16:07 bcherny

@bcherny - Thanks for providing another example of why this feature would be useful. If somebody would like to submit a PR, I'd be happy to review it.

However, I've realized that it's not actually as easy as I suggested previously. The problem with simply keeping the $ref property (or renaming it) is that the same object can be referenced in many different places, so which $ref do you keep?

Instead, it seems like we may need to add an array property (probably with a Symbol to avoid breaking changes) that contains all the refs that pointed to a particular object.

JamesMessinger avatar Jul 24 '20 09:07 JamesMessinger

The problem with simply keeping the $ref property (or renaming it) is that the same object can be referenced in many different places, so which $ref do you keep?

@JamesMessinger That's a great catch!

Thinking about it more, I wonder if a simpler and more general-purpose approach might be to avoid setting a property at all, and exposing an onDereference hook instead.

One possible API:

const result = await $RefParser.dereference('schema.json', {
  onDereference(originalSourceSchema, dereferencedSchema, originalRefPath, absoluteRefPath) {
    // ...
  }
})

A consumer might then use this API to build up a mapping:

const map = new Map

const result = await $RefParser.dereference('schema.json', {
  onDereference(originalSourceSchema, dereferencedSchema, originalRefPath, absoluteRefPath) {
    if (!map.has(dereferencedSchema)) {
      map.set(dereferencedSchema, new Set)
    }
    map.get(dereferencedSchema).add(originalRefPath)
  }
})

This approach has a few benefits over the Symbol-based approach:

  • It's strictly backwards-compatible, while I can come up with some convoluted cases where the Symbol-based approach is not
  • It more closely sticks to the JSON-Schema spec, and avoids having to monkey-patch in properties specific to json-schema-ref-parser (in practice, this would mostly affect consumers that use TypeScript)
  • It supports use cases that the Symbol-based approach does not (say, also collecting the original $ref-containing schemas)
  • It's extensible without additional breaking changes. eg. down the line we could introduce a 5th parameter for the onDereference callback, without breaking clients (not so for the Symbol-based approach)

bcherny avatar Jul 24 '20 15:07 bcherny

I like that idea @bcherny! What does everyone else think? Would this work for your use-case?

/cc @wenerme @maxtuzz @MikeRalphson @philsturgeon @ftheomunhoz

JamesMessinger avatar Jul 25 '20 08:07 JamesMessinger

Personally, I've only ever needed to know where a $ref used to be and not what locations previously pointed into a $refed component, and although using a callback would be a little more work (maintaining and comparing to a list of locations as you traversed the dereferenced object, or decorating it yourself), it certainly has the flexibility to enable those use-cases.

MikeRalphson avatar Jul 25 '20 12:07 MikeRalphson

This callback approach would work for the "live mock server wants to watch all $ref files for changes so it can reload" use-case. We'd just look file all filepaths and strip of anchors and watch that set of files.

philsturgeon avatar Jan 18 '21 18:01 philsturgeon

All conversations about changing how $ref should work should be done over here https://github.com/json-schema-org/referencing then when its decided we can all make sure tools follow it.

philsturgeon avatar Nov 26 '22 13:11 philsturgeon